Merge "Connect shortcuts to transitionInteractor and clean up KeyguardPreviewRenderer" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 158d914..e6c94d8 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -32,6 +32,7 @@
 import android.app.AlarmManager;
 import android.app.BroadcastOptions;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.Intent;
@@ -41,6 +42,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
@@ -81,6 +83,7 @@
 import android.os.UserHandle;
 import android.os.WearModeManagerInternal;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
@@ -109,6 +112,7 @@
 import com.android.server.deviceidle.IDeviceIdleConstraint;
 import com.android.server.deviceidle.TvConstraintController;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.utils.UserSettingDeviceConfigMediator;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -1020,7 +1024,8 @@
      * global Settings. Any access to this class or its fields should be done while
      * holding the DeviceIdleController lock.
      */
-    public final class Constants implements DeviceConfig.OnPropertiesChangedListener {
+    public final class Constants extends ContentObserver
+            implements DeviceConfig.OnPropertiesChangedListener {
         // Key names stored in the settings value.
         private static final String KEY_FLEX_TIME_SHORT = "flex_time_short";
         private static final String KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT =
@@ -1396,6 +1401,7 @@
         /**
          * Amount of time we would like to whitelist an app that is handling a
          * {@link android.app.PendingIntent} triggered by a {@link android.app.Notification}.
+         *
          * @see #KEY_NOTIFICATION_ALLOWLIST_DURATION_MS
          */
         public long NOTIFICATION_ALLOWLIST_DURATION_MS = mDefaultNotificationAllowlistDurationMs;
@@ -1413,9 +1419,14 @@
          */
         public boolean USE_MODE_MANAGER = mDefaultUseModeManager;
 
+        private final ContentResolver mResolver;
         private final boolean mSmallBatteryDevice;
+        private final UserSettingDeviceConfigMediator mUserSettingDeviceConfigMediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(',');
 
-        public Constants() {
+        public Constants(Handler handler, ContentResolver resolver) {
+            super(handler);
+            mResolver = resolver;
             initDefault();
             mSmallBatteryDevice = ActivityManager.isSmallBatteryDevice();
             if (mSmallBatteryDevice) {
@@ -1424,8 +1435,14 @@
             }
             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DEVICE_IDLE,
                     AppSchedulingModuleThread.getExecutor(), this);
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.DEVICE_IDLE_CONSTANTS),
+                    false, this);
             // Load all the constants.
-            onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_DEVICE_IDLE));
+            updateSettingsConstantLocked();
+            mUserSettingDeviceConfigMediator.setDeviceConfigProperties(
+                    DeviceConfig.getProperties(DeviceConfig.NAMESPACE_DEVICE_IDLE));
+            updateConstantsLocked();
         }
 
         private void initDefault() {
@@ -1574,188 +1591,166 @@
             return (!COMPRESS_TIME || defTimeout < compTimeout) ? defTimeout : compTimeout;
         }
 
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            synchronized (DeviceIdleController.this) {
+                updateSettingsConstantLocked();
+                updateConstantsLocked();
+            }
+        }
+
+        private void updateSettingsConstantLocked() {
+            try {
+                mUserSettingDeviceConfigMediator.setSettingsString(
+                        Settings.Global.getString(mResolver,
+                                Settings.Global.DEVICE_IDLE_CONSTANTS));
+            } catch (IllegalArgumentException e) {
+                // Failed to parse the settings string, log this and move on with previous values.
+                Slog.e(TAG, "Bad device idle settings", e);
+            }
+        }
 
         @Override
         public void onPropertiesChanged(DeviceConfig.Properties properties) {
             synchronized (DeviceIdleController.this) {
-                for (String name : properties.getKeyset()) {
-                    if (name == null) {
-                        continue;
-                    }
-                    switch (name) {
-                        case KEY_FLEX_TIME_SHORT:
-                            FLEX_TIME_SHORT = properties.getLong(
-                                    KEY_FLEX_TIME_SHORT, mDefaultFlexTimeShort);
-                            break;
-                        case KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT:
-                            LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = properties.getLong(
-                                    KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
-                                    mDefaultLightIdleAfterInactiveTimeout);
-                            break;
-                        case KEY_LIGHT_IDLE_TIMEOUT:
-                            LIGHT_IDLE_TIMEOUT = properties.getLong(
-                                    KEY_LIGHT_IDLE_TIMEOUT, mDefaultLightIdleTimeout);
-                            break;
-                        case KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX:
-                            LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = properties.getLong(
-                                    KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX,
-                                    mDefaultLightIdleTimeoutInitialFlex);
-                            break;
-                        case KEY_LIGHT_IDLE_TIMEOUT_MAX_FLEX:
-                            LIGHT_IDLE_TIMEOUT_MAX_FLEX = properties.getLong(
-                                    KEY_LIGHT_IDLE_TIMEOUT_MAX_FLEX,
-                                    mDefaultLightIdleTimeoutMaxFlex);
-                            break;
-                        case KEY_LIGHT_IDLE_FACTOR:
-                            LIGHT_IDLE_FACTOR = Math.max(1, properties.getFloat(
-                                    KEY_LIGHT_IDLE_FACTOR, mDefaultLightIdleFactor));
-                            break;
-                        case KEY_LIGHT_IDLE_INCREASE_LINEARLY:
-                            LIGHT_IDLE_INCREASE_LINEARLY = properties.getBoolean(
-                                    KEY_LIGHT_IDLE_INCREASE_LINEARLY,
-                                    mDefaultLightIdleIncreaseLinearly);
-                            break;
-                        case KEY_LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS:
-                            LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS = properties.getLong(
-                                    KEY_LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS,
-                                    mDefaultLightIdleLinearIncreaseFactorMs);
-                            break;
-                        case KEY_LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS:
-                            LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS = properties.getLong(
-                                    KEY_LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS,
-                                    mDefaultLightIdleFlexLinearIncreaseFactorMs);
-                            break;
-                        case KEY_LIGHT_MAX_IDLE_TIMEOUT:
-                            LIGHT_MAX_IDLE_TIMEOUT = properties.getLong(
-                                    KEY_LIGHT_MAX_IDLE_TIMEOUT, mDefaultLightMaxIdleTimeout);
-                            break;
-                        case KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET:
-                            LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = properties.getLong(
-                                    KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET,
-                                    mDefaultLightIdleMaintenanceMinBudget);
-                            break;
-                        case KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET:
-                            LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = properties.getLong(
-                                    KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET,
-                                    mDefaultLightIdleMaintenanceMaxBudget);
-                            break;
-                        case KEY_MIN_LIGHT_MAINTENANCE_TIME:
-                            MIN_LIGHT_MAINTENANCE_TIME = properties.getLong(
-                                    KEY_MIN_LIGHT_MAINTENANCE_TIME,
-                                    mDefaultMinLightMaintenanceTime);
-                            break;
-                        case KEY_MIN_DEEP_MAINTENANCE_TIME:
-                            MIN_DEEP_MAINTENANCE_TIME = properties.getLong(
-                                    KEY_MIN_DEEP_MAINTENANCE_TIME,
-                                    mDefaultMinDeepMaintenanceTime);
-                            break;
-                        case KEY_INACTIVE_TIMEOUT:
-                            final long defaultInactiveTimeout = mSmallBatteryDevice
-                                    ? DEFAULT_INACTIVE_TIMEOUT_SMALL_BATTERY
-                                    : mDefaultInactiveTimeout;
-                            INACTIVE_TIMEOUT = properties.getLong(
-                                    KEY_INACTIVE_TIMEOUT, defaultInactiveTimeout);
-                            break;
-                        case KEY_SENSING_TIMEOUT:
-                            SENSING_TIMEOUT = properties.getLong(
-                                    KEY_SENSING_TIMEOUT, mDefaultSensingTimeout);
-                            break;
-                        case KEY_LOCATING_TIMEOUT:
-                            LOCATING_TIMEOUT = properties.getLong(
-                                    KEY_LOCATING_TIMEOUT, mDefaultLocatingTimeout);
-                            break;
-                        case KEY_LOCATION_ACCURACY:
-                            LOCATION_ACCURACY = properties.getFloat(
-                                    KEY_LOCATION_ACCURACY, mDefaultLocationAccuracy);
-                            break;
-                        case KEY_MOTION_INACTIVE_TIMEOUT:
-                            MOTION_INACTIVE_TIMEOUT = properties.getLong(
-                                    KEY_MOTION_INACTIVE_TIMEOUT, mDefaultMotionInactiveTimeout);
-                            break;
-                        case KEY_MOTION_INACTIVE_TIMEOUT_FLEX:
-                            MOTION_INACTIVE_TIMEOUT_FLEX = properties.getLong(
-                                    KEY_MOTION_INACTIVE_TIMEOUT_FLEX,
-                                    mDefaultMotionInactiveTimeoutFlex);
-                            break;
-                        case KEY_IDLE_AFTER_INACTIVE_TIMEOUT:
-                            final long defaultIdleAfterInactiveTimeout = mSmallBatteryDevice
-                                    ? DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT_SMALL_BATTERY
-                                    : mDefaultIdleAfterInactiveTimeout;
-                            IDLE_AFTER_INACTIVE_TIMEOUT = properties.getLong(
-                                    KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
-                                    defaultIdleAfterInactiveTimeout);
-                            break;
-                        case KEY_IDLE_PENDING_TIMEOUT:
-                            IDLE_PENDING_TIMEOUT = properties.getLong(
-                                    KEY_IDLE_PENDING_TIMEOUT, mDefaultIdlePendingTimeout);
-                            break;
-                        case KEY_MAX_IDLE_PENDING_TIMEOUT:
-                            MAX_IDLE_PENDING_TIMEOUT = properties.getLong(
-                                    KEY_MAX_IDLE_PENDING_TIMEOUT, mDefaultMaxIdlePendingTimeout);
-                            break;
-                        case KEY_IDLE_PENDING_FACTOR:
-                            IDLE_PENDING_FACTOR = properties.getFloat(
-                                    KEY_IDLE_PENDING_FACTOR, mDefaultIdlePendingFactor);
-                            break;
-                        case KEY_QUICK_DOZE_DELAY_TIMEOUT:
-                            QUICK_DOZE_DELAY_TIMEOUT = properties.getLong(
-                                    KEY_QUICK_DOZE_DELAY_TIMEOUT, mDefaultQuickDozeDelayTimeout);
-                            break;
-                        case KEY_IDLE_TIMEOUT:
-                            IDLE_TIMEOUT = properties.getLong(
-                                    KEY_IDLE_TIMEOUT, mDefaultIdleTimeout);
-                            break;
-                        case KEY_MAX_IDLE_TIMEOUT:
-                            MAX_IDLE_TIMEOUT = properties.getLong(
-                                    KEY_MAX_IDLE_TIMEOUT, mDefaultMaxIdleTimeout);
-                            break;
-                        case KEY_IDLE_FACTOR:
-                            IDLE_FACTOR = properties.getFloat(KEY_IDLE_FACTOR, mDefaultIdleFactor);
-                            break;
-                        case KEY_MIN_TIME_TO_ALARM:
-                            MIN_TIME_TO_ALARM = properties.getLong(
-                                    KEY_MIN_TIME_TO_ALARM, mDefaultMinTimeToAlarm);
-                            break;
-                        case KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS:
-                            MAX_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
-                                    KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS,
-                                    mDefaultMaxTempAppAllowlistDurationMs);
-                            break;
-                        case KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS:
-                            MMS_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
-                                    KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS,
-                                    mDefaultMmsTempAppAllowlistDurationMs);
-                            break;
-                        case KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS:
-                            SMS_TEMP_APP_ALLOWLIST_DURATION_MS = properties.getLong(
-                                    KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS,
-                                    mDefaultSmsTempAppAllowlistDurationMs);
-                            break;
-                        case KEY_NOTIFICATION_ALLOWLIST_DURATION_MS:
-                            NOTIFICATION_ALLOWLIST_DURATION_MS = properties.getLong(
-                                    KEY_NOTIFICATION_ALLOWLIST_DURATION_MS,
-                                    mDefaultNotificationAllowlistDurationMs);
-                            break;
-                        case KEY_WAIT_FOR_UNLOCK:
-                            WAIT_FOR_UNLOCK = properties.getBoolean(
-                                    KEY_WAIT_FOR_UNLOCK, mDefaultWaitForUnlock);
-                            break;
-                        case KEY_USE_WINDOW_ALARMS:
-                            USE_WINDOW_ALARMS = properties.getBoolean(
-                                    KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms);
-                            break;
-                        case KEY_USE_MODE_MANAGER:
-                            USE_MODE_MANAGER = properties.getBoolean(
-                                    KEY_USE_MODE_MANAGER, mDefaultUseModeManager);
-                            break;
-                        default:
-                            Slog.e(TAG, "Unknown configuration key: " + name);
-                            break;
-                    }
-                }
+                mUserSettingDeviceConfigMediator.setDeviceConfigProperties(properties);
+                updateConstantsLocked();
             }
         }
 
+        private void updateConstantsLocked() {
+            if (mSmallBatteryDevice) return;
+            FLEX_TIME_SHORT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_FLEX_TIME_SHORT, mDefaultFlexTimeShort);
+
+            LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
+                    mDefaultLightIdleAfterInactiveTimeout);
+
+            LIGHT_IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_TIMEOUT, mDefaultLightIdleTimeout);
+
+            LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX,
+                    mDefaultLightIdleTimeoutInitialFlex);
+
+            LIGHT_IDLE_TIMEOUT_MAX_FLEX = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_TIMEOUT_MAX_FLEX,
+                    mDefaultLightIdleTimeoutMaxFlex);
+
+            LIGHT_IDLE_FACTOR = Math.max(1, mUserSettingDeviceConfigMediator.getFloat(
+                    KEY_LIGHT_IDLE_FACTOR, mDefaultLightIdleFactor));
+
+            LIGHT_IDLE_INCREASE_LINEARLY = mUserSettingDeviceConfigMediator.getBoolean(
+                    KEY_LIGHT_IDLE_INCREASE_LINEARLY,
+                    mDefaultLightIdleIncreaseLinearly);
+
+            LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_LINEAR_INCREASE_FACTOR_MS,
+                    mDefaultLightIdleLinearIncreaseFactorMs);
+
+            LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_FLEX_LINEAR_INCREASE_FACTOR_MS,
+                    mDefaultLightIdleFlexLinearIncreaseFactorMs);
+
+            LIGHT_MAX_IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_MAX_IDLE_TIMEOUT, mDefaultLightMaxIdleTimeout);
+
+            LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET,
+                    mDefaultLightIdleMaintenanceMinBudget);
+
+            LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET,
+                    mDefaultLightIdleMaintenanceMaxBudget);
+
+            MIN_LIGHT_MAINTENANCE_TIME = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MIN_LIGHT_MAINTENANCE_TIME,
+                    mDefaultMinLightMaintenanceTime);
+
+            MIN_DEEP_MAINTENANCE_TIME = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MIN_DEEP_MAINTENANCE_TIME,
+                    mDefaultMinDeepMaintenanceTime);
+
+            final long defaultInactiveTimeout = mSmallBatteryDevice
+                    ? DEFAULT_INACTIVE_TIMEOUT_SMALL_BATTERY
+                    : mDefaultInactiveTimeout;
+            INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_INACTIVE_TIMEOUT, defaultInactiveTimeout);
+
+            SENSING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_SENSING_TIMEOUT, mDefaultSensingTimeout);
+
+            LOCATING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_LOCATING_TIMEOUT, mDefaultLocatingTimeout);
+
+            LOCATION_ACCURACY = mUserSettingDeviceConfigMediator.getFloat(
+                    KEY_LOCATION_ACCURACY, mDefaultLocationAccuracy);
+
+            MOTION_INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MOTION_INACTIVE_TIMEOUT, mDefaultMotionInactiveTimeout);
+
+            MOTION_INACTIVE_TIMEOUT_FLEX = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MOTION_INACTIVE_TIMEOUT_FLEX,
+                    mDefaultMotionInactiveTimeoutFlex);
+
+            final long defaultIdleAfterInactiveTimeout = mSmallBatteryDevice
+                    ? DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT_SMALL_BATTERY
+                    : mDefaultIdleAfterInactiveTimeout;
+            IDLE_AFTER_INACTIVE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
+                    defaultIdleAfterInactiveTimeout);
+
+            IDLE_PENDING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_IDLE_PENDING_TIMEOUT, mDefaultIdlePendingTimeout);
+
+            MAX_IDLE_PENDING_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MAX_IDLE_PENDING_TIMEOUT, mDefaultMaxIdlePendingTimeout);
+
+            IDLE_PENDING_FACTOR = mUserSettingDeviceConfigMediator.getFloat(
+                    KEY_IDLE_PENDING_FACTOR, mDefaultIdlePendingFactor);
+
+            QUICK_DOZE_DELAY_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_QUICK_DOZE_DELAY_TIMEOUT, mDefaultQuickDozeDelayTimeout);
+
+            IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_IDLE_TIMEOUT, mDefaultIdleTimeout);
+
+            MAX_IDLE_TIMEOUT = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MAX_IDLE_TIMEOUT, mDefaultMaxIdleTimeout);
+
+            IDLE_FACTOR = mUserSettingDeviceConfigMediator.getFloat(KEY_IDLE_FACTOR,
+                    mDefaultIdleFactor);
+
+            MIN_TIME_TO_ALARM = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MIN_TIME_TO_ALARM, mDefaultMinTimeToAlarm);
+
+            MAX_TEMP_APP_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MAX_TEMP_APP_ALLOWLIST_DURATION_MS,
+                    mDefaultMaxTempAppAllowlistDurationMs);
+
+            MMS_TEMP_APP_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_MMS_TEMP_APP_ALLOWLIST_DURATION_MS,
+                    mDefaultMmsTempAppAllowlistDurationMs);
+
+            SMS_TEMP_APP_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_SMS_TEMP_APP_ALLOWLIST_DURATION_MS,
+                    mDefaultSmsTempAppAllowlistDurationMs);
+
+            NOTIFICATION_ALLOWLIST_DURATION_MS = mUserSettingDeviceConfigMediator.getLong(
+                    KEY_NOTIFICATION_ALLOWLIST_DURATION_MS,
+                    mDefaultNotificationAllowlistDurationMs);
+
+            WAIT_FOR_UNLOCK = mUserSettingDeviceConfigMediator.getBoolean(
+                    KEY_WAIT_FOR_UNLOCK, mDefaultWaitForUnlock);
+
+            USE_WINDOW_ALARMS = mUserSettingDeviceConfigMediator.getBoolean(
+                    KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms);
+
+            USE_MODE_MANAGER = mUserSettingDeviceConfigMediator.getBoolean(
+                    KEY_USE_MODE_MANAGER, mDefaultUseModeManager);
+        }
+
         void dump(PrintWriter pw) {
             pw.println("  Settings:");
 
@@ -2490,9 +2485,10 @@
             return mConnectivityManager;
         }
 
-        Constants getConstants(DeviceIdleController controller) {
+        Constants getConstants(DeviceIdleController controller, Handler handler,
+                ContentResolver resolver) {
             if (mConstants == null) {
-                mConstants = controller.new Constants();
+                mConstants = controller.new Constants(handler, resolver);
             }
             return mConstants;
         }
@@ -2650,7 +2646,7 @@
                 }
             }
 
-            mConstants = mInjector.getConstants(this);
+            mConstants = mInjector.getConstants(this, mHandler, getContext().getContentResolver());
 
             readConfigFileLocked();
             updateWhitelistAppIdsLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index d2150b8..8381d1a 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -109,7 +109,6 @@
 import android.content.ContentResolver;
 import android.provider.DeviceConfig;
 import android.util.IndentingPrintWriter;
-import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -154,7 +153,6 @@
     private long mMinSatiatedConsumptionLimit;
     private long mMaxSatiatedConsumptionLimit;
 
-    private final KeyValueListParser mParser = new KeyValueListParser(',');
     private final Injector mInjector;
 
     private final SparseArray<Action> mActions = new SparseArray<>();
@@ -241,35 +239,36 @@
         mRewards.clear();
 
         try {
-            mParser.setString(policyValuesString);
+            mUserSettingDeviceConfigMediator.setSettingsString(policyValuesString);
+            mUserSettingDeviceConfigMediator.setDeviceConfigProperties(properties);
         } catch (IllegalArgumentException e) {
             Slog.e(TAG, "Global setting key incorrect: ", e);
         }
 
-        mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceOther = getConstantAsCake(
             KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES);
-        mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(
                 KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
                 DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES,
                 mMinSatiatedBalanceOther);
-        mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceExempted = getConstantAsCake(
                 KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED,
                 DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
                 mMinSatiatedBalanceHeadlessSystemApp);
-        mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
+        mMaxSatiatedBalance = getConstantAsCake(
             KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
             Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
-        mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mMinSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_AM_MIN_CONSUMPTION_LIMIT, DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES,
                 arcToCake(1));
-        mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mInitialSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
                 mMinSatiatedConsumptionLimit);
-        mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mMaxSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_AM_MAX_CONSUMPTION_LIMIT, DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES,
                 mInitialSatiatedConsumptionLimit);
 
-        final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(mParser, properties,
+        final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE,
                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES);
 
@@ -279,49 +278,49 @@
         // run out of credits, and not when the system has run out of stock.
         mActions.put(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE,
                 new Action(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES),
                         exactAllowWhileIdleWakeupBasePrice,
                         /* respectsStockLimit */ false));
         mActions.put(ACTION_ALARM_WAKEUP_EXACT,
                 new Action(ACTION_ALARM_WAKEUP_EXACT,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_EXACT_WAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE,
                                 DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES),
                         /* respectsStockLimit */ false));
 
         final long inexactAllowWhileIdleWakeupBasePrice =
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE,
                         DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_BASE_PRICE_CAKES);
 
         mActions.put(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE,
                 new Action(ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES),
                         inexactAllowWhileIdleWakeupBasePrice,
                         /* respectsStockLimit */ false));
         mActions.put(ACTION_ALARM_WAKEUP_INEXACT,
                 new Action(ACTION_ALARM_WAKEUP_INEXACT,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE,
                                 DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES),
                         /* respectsStockLimit */ false));
 
-        final long exactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties,
+        final long exactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE,
                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES);
         mActions.put(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE,
                 new Action(ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES),
                         exactAllowWhileIdleNonWakeupBasePrice,
@@ -329,18 +328,18 @@
 
         mActions.put(ACTION_ALARM_NONWAKEUP_EXACT,
                 new Action(ACTION_ALARM_NONWAKEUP_EXACT,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE,
                                 DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES),
                         /* respectsStockLimit */ false));
 
-        final long inexactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties,
+        final long inexactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE,
                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE_CAKES);
-        final long inexactAllowWhileIdleNonWakeupCtp = getConstantAsCake(mParser, properties,
+        final long inexactAllowWhileIdleNonWakeupCtp = getConstantAsCake(
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP,
                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_CTP_CAKES);
         mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE,
@@ -350,72 +349,72 @@
 
         mActions.put(ACTION_ALARM_NONWAKEUP_INEXACT,
                 new Action(ACTION_ALARM_NONWAKEUP_INEXACT,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE,
                                 DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_BASE_PRICE_CAKES)));
         mActions.put(ACTION_ALARM_CLOCK,
                 new Action(ACTION_ALARM_CLOCK,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_ALARMCLOCK_CTP,
                                 DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE,
                                 DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES),
                         /* respectsStockLimit */ false));
 
         mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_TOP_ACTIVITY_INSTANT,
                         DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_TOP_ACTIVITY_ONGOING,
                         DEFAULT_AM_REWARD_TOP_ACTIVITY_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_TOP_ACTIVITY_MAX,
                         DEFAULT_AM_REWARD_TOP_ACTIVITY_MAX_CAKES)));
         mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_NOTIFICATION_SEEN_INSTANT,
                         DEFAULT_AM_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_NOTIFICATION_SEEN_ONGOING,
                         DEFAULT_AM_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_NOTIFICATION_SEEN_MAX,
                         DEFAULT_AM_REWARD_NOTIFICATION_SEEN_MAX_CAKES)));
         mRewards.put(REWARD_NOTIFICATION_INTERACTION,
                 new Reward(REWARD_NOTIFICATION_INTERACTION,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT,
                                 DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING,
                                 DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_NOTIFICATION_INTERACTION_MAX,
                                 DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES)));
         mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_WIDGET_INTERACTION_INSTANT,
                         DEFAULT_AM_REWARD_WIDGET_INTERACTION_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_WIDGET_INTERACTION_ONGOING,
                         DEFAULT_AM_REWARD_WIDGET_INTERACTION_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_AM_REWARD_WIDGET_INTERACTION_MAX,
                         DEFAULT_AM_REWARD_WIDGET_INTERACTION_MAX_CAKES)));
         mRewards.put(REWARD_OTHER_USER_INTERACTION,
                 new Reward(REWARD_OTHER_USER_INTERACTION,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_OTHER_USER_INTERACTION_INSTANT,
                                 DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_OTHER_USER_INTERACTION_ONGOING,
                                 DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_AM_REWARD_OTHER_USER_INTERACTION_MAX,
                                 DEFAULT_AM_REWARD_OTHER_USER_INTERACTION_MAX_CAKES)));
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index a4043dd..61096b9 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -33,9 +33,9 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.IndentingPrintWriter;
-import android.util.KeyValueListParser;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.UserSettingDeviceConfigMediator;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -197,10 +197,17 @@
     }
 
     protected final InternalResourceService mIrs;
+    protected final UserSettingDeviceConfigMediator mUserSettingDeviceConfigMediator;
     private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS];
 
     EconomicPolicy(@NonNull InternalResourceService irs) {
         mIrs = irs;
+        // Don't cross the streams! Mixing Settings/local user config changes with DeviceConfig
+        // config can cause issues since the scales may be different, so use one or the other.
+        // If user settings exist, then just stick with the Settings constants, even if there
+        // are invalid values.
+        mUserSettingDeviceConfigMediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesAllMediator(',');
         for (int mId : getCostModifiers()) {
             initModifier(mId, irs);
         }
@@ -464,28 +471,14 @@
         return "UNKNOWN_REWARD:" + Integer.toHexString(eventId);
     }
 
-    protected long getConstantAsCake(@NonNull KeyValueListParser parser,
-            @Nullable DeviceConfig.Properties properties, String key, long defaultValCake) {
-        return getConstantAsCake(parser, properties, key, defaultValCake, 0);
+    protected long getConstantAsCake(String key, long defaultValCake) {
+        return getConstantAsCake(key, defaultValCake, 0);
     }
 
-    protected long getConstantAsCake(@NonNull KeyValueListParser parser,
-            @Nullable DeviceConfig.Properties properties, String key, long defaultValCake,
-            long minValCake) {
-        // Don't cross the streams! Mixing Settings/local user config changes with DeviceConfig
-        // config can cause issues since the scales may be different, so use one or the other.
-        if (parser.size() > 0) {
-            // User settings take precedence. Just stick with the Settings constants, even if there
-            // are invalid values. It's not worth the time to evaluate all the key/value pairs to
-            // make sure there are valid ones before deciding.
-            return Math.max(minValCake,
-                parseCreditValue(parser.getString(key, null), defaultValCake));
-        }
-        if (properties != null) {
-            return Math.max(minValCake,
-                parseCreditValue(properties.getString(key, null), defaultValCake));
-        }
-        return Math.max(minValCake, defaultValCake);
+    protected long getConstantAsCake(String key, long defaultValCake, long minValCake) {
+        return Math.max(minValCake,
+                parseCreditValue(
+                        mUserSettingDeviceConfigMediator.getString(key, null), defaultValCake));
     }
 
     @VisibleForTesting
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 91a291f..69e5736 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -127,7 +127,6 @@
 import android.content.ContentResolver;
 import android.provider.DeviceConfig;
 import android.util.IndentingPrintWriter;
-import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -168,7 +167,6 @@
     private long mMinSatiatedConsumptionLimit;
     private long mMaxSatiatedConsumptionLimit;
 
-    private final KeyValueListParser mParser = new KeyValueListParser(',');
     private final Injector mInjector;
 
     private final SparseArray<Action> mActions = new SparseArray<>();
@@ -274,176 +272,177 @@
         mRewards.clear();
 
         try {
-            mParser.setString(policyValuesString);
+            mUserSettingDeviceConfigMediator.setSettingsString(policyValuesString);
+            mUserSettingDeviceConfigMediator.setDeviceConfigProperties(properties);
         } catch (IllegalArgumentException e) {
             Slog.e(TAG, "Global setting key incorrect: ", e);
         }
 
-        mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceOther = getConstantAsCake(
             KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES);
-        mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(
                 KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
                 DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES,
                 mMinSatiatedBalanceOther);
-        mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceExempted = getConstantAsCake(
                 KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED,
                 DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
                 mMinSatiatedBalanceHeadlessSystemApp);
-        mMinSatiatedBalanceIncrementalAppUpdater = getConstantAsCake(mParser, properties,
+        mMinSatiatedBalanceIncrementalAppUpdater = getConstantAsCake(
                 KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
                 DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES);
-        mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
+        mMaxSatiatedBalance = getConstantAsCake(
             KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
             Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
-        mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mMinSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_JS_MIN_CONSUMPTION_LIMIT, DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES,
                 arcToCake(1));
-        mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mInitialSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
                 mMinSatiatedConsumptionLimit);
-        mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+        mMaxSatiatedConsumptionLimit = getConstantAsCake(
                 KEY_JS_MAX_CONSUMPTION_LIMIT, DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES,
                 mInitialSatiatedConsumptionLimit);
 
         mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MAX_START_CTP,
                         DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MAX_START_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_MAX_START_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_MAX_RUNNING, new Action(ACTION_JOB_MAX_RUNNING,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MAX_RUNNING_CTP,
                         DEFAULT_JS_ACTION_JOB_MAX_RUNNING_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_MAX_RUNNING_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_HIGH_START, new Action(ACTION_JOB_HIGH_START,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_HIGH_START_CTP,
                         DEFAULT_JS_ACTION_JOB_HIGH_START_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_HIGH_START_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_HIGH_START_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_HIGH_RUNNING, new Action(ACTION_JOB_HIGH_RUNNING,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_HIGH_RUNNING_CTP,
                         DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_HIGH_RUNNING_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_DEFAULT_START, new Action(ACTION_JOB_DEFAULT_START,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_DEFAULT_START_CTP,
                         DEFAULT_JS_ACTION_JOB_DEFAULT_START_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_DEFAULT_START_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_DEFAULT_RUNNING, new Action(ACTION_JOB_DEFAULT_RUNNING,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_DEFAULT_RUNNING_CTP,
                         DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_DEFAULT_RUNNING_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_LOW_START, new Action(ACTION_JOB_LOW_START,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_LOW_START_CTP,
                         DEFAULT_JS_ACTION_JOB_LOW_START_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_LOW_START_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_LOW_START_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_LOW_RUNNING, new Action(ACTION_JOB_LOW_RUNNING,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_LOW_RUNNING_CTP,
                         DEFAULT_JS_ACTION_JOB_LOW_RUNNING_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_MIN_START, new Action(ACTION_JOB_MIN_START,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MIN_START_CTP,
                         DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MIN_START_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_MIN_START_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_MIN_RUNNING, new Action(ACTION_JOB_MIN_RUNNING,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MIN_RUNNING_CTP,
                         DEFAULT_JS_ACTION_JOB_MIN_RUNNING_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES)));
         mActions.put(ACTION_JOB_TIMEOUT, new Action(ACTION_JOB_TIMEOUT,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP,
                         DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE,
                         DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES)));
 
         mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_TOP_ACTIVITY_INSTANT,
                         DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_TOP_ACTIVITY_ONGOING,
                         DEFAULT_JS_REWARD_TOP_ACTIVITY_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_TOP_ACTIVITY_MAX,
                         DEFAULT_JS_REWARD_TOP_ACTIVITY_MAX_CAKES)));
         mRewards.put(REWARD_NOTIFICATION_SEEN, new Reward(REWARD_NOTIFICATION_SEEN,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_NOTIFICATION_SEEN_INSTANT,
                         DEFAULT_JS_REWARD_NOTIFICATION_SEEN_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_NOTIFICATION_SEEN_ONGOING,
                         DEFAULT_JS_REWARD_NOTIFICATION_SEEN_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_NOTIFICATION_SEEN_MAX,
                         DEFAULT_JS_REWARD_NOTIFICATION_SEEN_MAX_CAKES)));
         mRewards.put(REWARD_NOTIFICATION_INTERACTION,
                 new Reward(REWARD_NOTIFICATION_INTERACTION,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT,
                                 DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING,
                                 DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_ONGOING_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_NOTIFICATION_INTERACTION_MAX,
                                 DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES)));
         mRewards.put(REWARD_WIDGET_INTERACTION, new Reward(REWARD_WIDGET_INTERACTION,
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_WIDGET_INTERACTION_INSTANT,
                         DEFAULT_JS_REWARD_WIDGET_INTERACTION_INSTANT_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_WIDGET_INTERACTION_ONGOING,
                         DEFAULT_JS_REWARD_WIDGET_INTERACTION_ONGOING_CAKES),
-                getConstantAsCake(mParser, properties,
+                getConstantAsCake(
                         KEY_JS_REWARD_WIDGET_INTERACTION_MAX,
                         DEFAULT_JS_REWARD_WIDGET_INTERACTION_MAX_CAKES)));
         mRewards.put(REWARD_OTHER_USER_INTERACTION,
                 new Reward(REWARD_OTHER_USER_INTERACTION,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_OTHER_USER_INTERACTION_INSTANT,
                                 DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_INSTANT_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_OTHER_USER_INTERACTION_ONGOING,
                                 DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_OTHER_USER_INTERACTION_MAX,
                                 DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES)));
         mRewards.put(REWARD_APP_INSTALL,
                 new Reward(REWARD_APP_INSTALL,
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_APP_INSTALL_INSTANT,
                                 DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_APP_INSTALL_ONGOING,
                                 DEFAULT_JS_REWARD_APP_INSTALL_ONGOING_CAKES),
-                        getConstantAsCake(mParser, properties,
+                        getConstantAsCake(
                                 KEY_JS_REWARD_APP_INSTALL_MAX,
                                 DEFAULT_JS_REWARD_APP_INSTALL_MAX_CAKES)));
     }
diff --git a/core/api/current.txt b/core/api/current.txt
index 9d13d8a..805ecd4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9342,6 +9342,7 @@
     method public String getClassName();
     method public android.content.res.Configuration getConfiguration();
     method public int getEventType();
+    method @FlaggedApi("android.app.usage.user_interaction_type_api") @NonNull public android.os.PersistableBundle getExtras();
     method public String getPackageName();
     method public String getShortcutId();
     method public long getTimeStamp();
@@ -9407,6 +9408,8 @@
     method @FlaggedApi("android.app.usage.filter_based_event_query_api") @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery);
     method public android.app.usage.UsageEvents queryEventsForSelf(long, long);
     method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
+    field @FlaggedApi("android.app.usage.user_interaction_type_api") public static final String EXTRA_EVENT_ACTION = "android.app.usage.extra.EVENT_ACTION";
+    field @FlaggedApi("android.app.usage.user_interaction_type_api") public static final String EXTRA_EVENT_CATEGORY = "android.app.usage.extra.EVENT_CATEGORY";
     field public static final int INTERVAL_BEST = 4; // 0x4
     field public static final int INTERVAL_DAILY = 0; // 0x0
     field public static final int INTERVAL_MONTHLY = 2; // 0x2
@@ -9851,7 +9854,7 @@
     method @NonNull public android.content.AttributionSource build();
     method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
     method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
-    method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
+    method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
     method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
     method @NonNull public android.content.AttributionSource.Builder setPid(int);
@@ -10598,6 +10601,7 @@
     field public static final String RESTRICTIONS_SERVICE = "restrictions";
     field public static final String ROLE_SERVICE = "role";
     field public static final String SEARCH_SERVICE = "search";
+    field @FlaggedApi("android.os.security_state_service") public static final String SECURITY_STATE_SERVICE = "security_state";
     field public static final String SENSOR_SERVICE = "sensor";
     field public static final String SHORTCUT_SERVICE = "shortcut";
     field public static final String STATUS_BAR_SERVICE = "statusbar";
@@ -10610,6 +10614,7 @@
     field public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service";
     field public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification";
     field public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String TV_AD_SERVICE = "tv_ad";
     field public static final String TV_INPUT_SERVICE = "tv_input";
     field public static final String TV_INTERACTIVE_APP_SERVICE = "tv_interactive_app";
     field public static final String UI_MODE_SERVICE = "uimode";
@@ -11098,6 +11103,7 @@
     field public static final String ACTION_UID_REMOVED = "android.intent.action.UID_REMOVED";
     field @Deprecated public static final String ACTION_UMS_CONNECTED = "android.intent.action.UMS_CONNECTED";
     field @Deprecated public static final String ACTION_UMS_DISCONNECTED = "android.intent.action.UMS_DISCONNECTED";
+    field @FlaggedApi("android.content.pm.archiving") public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
     field @Deprecated public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
     field public static final String ACTION_USER_BACKGROUND = "android.intent.action.USER_BACKGROUND";
     field public static final String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND";
@@ -12363,6 +12369,7 @@
   public class PackageInfo implements android.os.Parcelable {
     ctor public PackageInfo();
     method public int describeContents();
+    method @FlaggedApi("android.content.pm.archiving") public long getArchiveTimeMillis();
     method public long getLongVersionCode();
     method public void setLongVersionCode(long);
     method public void writeToParcel(android.os.Parcel, int);
@@ -12419,6 +12426,9 @@
     method @NonNull public android.content.pm.PackageInstaller.Session openSession(int) throws java.io.IOException;
     method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
     method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler);
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender);
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender);
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, int, @NonNull android.content.IntentSender);
@@ -12440,6 +12450,10 @@
     field public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS";
     field public static final String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE";
     field public static final String EXTRA_STORAGE_PATH = "android.content.pm.extra.STORAGE_PATH";
+    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID";
+    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
     field public static final int PACKAGE_SOURCE_DOWNLOADED_FILE = 4; // 0x4
     field public static final int PACKAGE_SOURCE_LOCAL_FILE = 3; // 0x3
     field public static final int PACKAGE_SOURCE_OTHER = 1; // 0x1
@@ -12455,6 +12469,13 @@
     field public static final int STATUS_FAILURE_TIMEOUT = 8; // 0x8
     field public static final int STATUS_PENDING_USER_ACTION = -1; // 0xffffffff
     field public static final int STATUS_SUCCESS = 0; // 0x0
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; // 0x4
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; // 0x5
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; // 0x2
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; // 0x3
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; // 0x1
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_GENERIC_ERROR = 100; // 0x64
+    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_OK = 0; // 0x0
   }
 
   public static final class PackageInstaller.InstallConstraints implements android.os.Parcelable {
@@ -12618,6 +12639,7 @@
     method @RequiresPermission(android.Manifest.permission.ENFORCE_UPDATE_OWNERSHIP) public void setRequestUpdateOwnership(boolean);
     method public void setRequireUserAction(int);
     method public void setSize(long);
+    method @FlaggedApi("android.content.pm.archiving") public void setUnarchiveId(int);
     method public void setWhitelistedRestrictedPermissions(@Nullable java.util.Set<java.lang.String>);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionParams> CREATOR;
@@ -12647,6 +12669,7 @@
     method public void writeToParcel(android.os.Parcel, int);
     field public int banner;
     field public int icon;
+    field @FlaggedApi("android.content.pm.archiving") public boolean isArchived;
     field public int labelRes;
     field public int logo;
     field public android.os.Bundle metaData;
@@ -12769,6 +12792,7 @@
     method public boolean hasSigningCertificate(int, @NonNull byte[], int);
     method public abstract boolean hasSystemFeature(@NonNull String);
     method public abstract boolean hasSystemFeature(@NonNull String, int);
+    method @FlaggedApi("android.content.pm.archiving") public boolean isAppArchivable(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(value="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS", conditional=true) public boolean isAutoRevokeWhitelisted(@NonNull String);
     method public boolean isAutoRevokeWhitelisted();
     method public boolean isDefaultApplicationIcon(@NonNull android.graphics.drawable.Drawable);
@@ -13010,6 +13034,7 @@
     field public static final int INSTALL_SCENARIO_FAST = 1; // 0x1
     field public static final int MATCH_ALL = 131072; // 0x20000
     field public static final int MATCH_APEX = 1073741824; // 0x40000000
+    field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
     field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000
     field public static final int MATCH_DIRECT_BOOT_AUTO = 268435456; // 0x10000000
     field public static final int MATCH_DIRECT_BOOT_AWARE = 524288; // 0x80000
@@ -22031,6 +22056,19 @@
     method public void onJetUserIdUpdate(android.media.JetPlayer, int, int);
   }
 
+  @FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecConfigurator {
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") public void addMediaCodec(@NonNull android.media.MediaCodec);
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create();
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create(@NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener);
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.AudioTrack, @NonNull android.media.MediaCodec);
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") public void removeMediaCodec(@NonNull android.media.MediaCodec);
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") public void setAudioTrack(@Nullable android.media.AudioTrack);
+  }
+
+  @FlaggedApi("android.media.audio.loudness_configurator_api") public static interface LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener {
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public default android.os.Bundle onLoudnessCodecUpdate(@NonNull android.media.MediaCodec, @NonNull android.os.Bundle);
+  }
+
   public class MediaActionSound {
     ctor public MediaActionSound();
     method public void load(int);
@@ -27299,6 +27337,13 @@
 
 }
 
+package android.media.tv.ad {
+
+  @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdManager {
+  }
+
+}
+
 package android.media.tv.interactive {
 
   public final class AppLinkInfo implements android.os.Parcelable {
@@ -32837,7 +32882,7 @@
     method public static android.os.Message obtain(android.os.Handler, int, Object);
     method public static android.os.Message obtain(android.os.Handler, int, int, int);
     method public static android.os.Message obtain(android.os.Handler, int, int, int, Object);
-    method public android.os.Bundle peekData();
+    method @Nullable public android.os.Bundle peekData();
     method public void recycle();
     method public void sendToTarget();
     method public void setAsynchronous(boolean);
@@ -33376,6 +33421,13 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.os.ResultReceiver> CREATOR;
   }
 
+  @FlaggedApi("android.os.security_state_service") public class SecurityStateManager {
+    method @FlaggedApi("android.os.security_state_service") @NonNull public android.os.Bundle getGlobalSecurityState();
+    field public static final String KEY_KERNEL_VERSION = "kernel_version";
+    field public static final String KEY_SYSTEM_SPL = "system_spl";
+    field public static final String KEY_VENDOR_SPL = "vendor_spl";
+  }
+
   public final class SharedMemory implements java.io.Closeable android.os.Parcelable {
     method public void close();
     method @NonNull public static android.os.SharedMemory create(@Nullable String, int) throws android.system.ErrnoException;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ce5752f..e2b2879 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -258,6 +258,7 @@
     field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION";
     field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
     field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
+    field @FlaggedApi("android.permission.flags.factory_reset_prep_permission_apis") public static final String PREPARE_FACTORY_RESET = "android.permission.PREPARE_FACTORY_RESET";
     field public static final String PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE = "android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE";
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
@@ -3613,7 +3614,6 @@
     field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
     field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
     field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
-    field @FlaggedApi("android.content.pm.archiving") public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
     field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
     field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
     field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
@@ -3861,16 +3861,9 @@
     field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800
   }
 
-  public class PackageInfo implements android.os.Parcelable {
-    method @FlaggedApi("android.content.pm.archiving") public long getArchiveTimeMillis();
-  }
-
   public class PackageInstaller {
     method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
     method @FlaggedApi("android.content.pm.read_install_info") @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
-    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
     field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
     field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3881,23 +3874,12 @@
     field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
     field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
     field @Deprecated public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH";
-    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
-    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID";
-    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
-    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
     field public static final int LOCATION_DATA_APP = 0; // 0x0
     field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
     field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
     field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0
     field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1
     field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2
-    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; // 0x4
-    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; // 0x5
-    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; // 0x2
-    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; // 0x3
-    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; // 0x1
-    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_GENERIC_ERROR = 100; // 0x64
-    field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_OK = 0; // 0x0
   }
 
   public static class PackageInstaller.InstallInfo {
@@ -3947,14 +3929,12 @@
     method public void setRequestDowngrade(boolean);
     method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
-    method @FlaggedApi("android.content.pm.archiving") public void setUnarchiveId(int);
   }
 
   public class PackageItemInfo {
     method public static void forceSafeLabels();
     method @Deprecated @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager);
     method @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager, @FloatRange(from=0) float, int);
-    field @FlaggedApi("android.content.pm.archiving") public boolean isArchived;
   }
 
   public abstract class PackageManager {
@@ -3986,7 +3966,6 @@
     method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @Deprecated public abstract int installExistingPackage(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public abstract int installExistingPackage(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @FlaggedApi("android.content.pm.archiving") public boolean isAppArchivable(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, int, android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
@@ -4104,7 +4083,6 @@
     field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
     field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
     field public static final int MATCH_ANY_USER = 4194304; // 0x400000
-    field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
     field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
     field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
     field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4f8e8dd..014ddd41 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3484,9 +3484,20 @@
         // only do this if the user already has more than one preferred locale
         if (android.content.res.Flags.defaultLocale()
                 && r.getConfiguration().getLocales().size() > 1) {
-            LocaleConfig lc = getUserId() < 0
-                    ? LocaleConfig.fromContextIgnoringOverride(this)
-                    : new LocaleConfig(this);
+            LocaleConfig lc;
+            if (getUserId() < 0) {
+                lc = LocaleConfig.fromContextIgnoringOverride(this);
+            } else {
+                // This is needed because the app might have locale config overrides that need to
+                // be read from disk in order for resources to correctly choose which values to
+                // load.
+                StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
+                try {
+                    lc = new LocaleConfig(this);
+                } finally {
+                    StrictMode.setThreadPolicy(policy);
+                }
+            }
             mResourcesManager.setLocaleConfig(lc);
         }
     }
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 6009c29..24a5157 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -159,7 +159,8 @@
      * Loads {@link ApkAssets} and caches them to prevent their garbage collection while the
      * instance is alive and reachable.
      */
-    private class ApkAssetsSupplier {
+    @VisibleForTesting
+    protected class ApkAssetsSupplier {
         final ArrayMap<ApkKey, ApkAssets> mLocalCache = new ArrayMap<>();
 
         /**
@@ -544,7 +545,10 @@
      * from an {@link ApkAssetsSupplier} if non-null; otherwise ApkAssets are loaded using
      * {@link #loadApkAssets(ApkKey)}.
      */
-    private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
+
+    @VisibleForTesting
+    @UnsupportedAppUsage
+    protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
             @Nullable ApkAssetsSupplier apkSupplier) {
         final AssetManager.Builder builder = new AssetManager.Builder();
 
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 79a5879..9cf732a 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -137,6 +137,8 @@
 import android.media.soundtrigger.SoundTriggerManager;
 import android.media.tv.ITvInputManager;
 import android.media.tv.TvInputManager;
+import android.media.tv.ad.ITvAdManager;
+import android.media.tv.ad.TvAdManager;
 import android.media.tv.interactive.ITvInteractiveAppManager;
 import android.media.tv.interactive.TvInteractiveAppManager;
 import android.media.tv.tunerresourcemanager.ITunerResourceManager;
@@ -174,6 +176,7 @@
 import android.os.IPowerManager;
 import android.os.IPowerStatsService;
 import android.os.IRecoverySystem;
+import android.os.ISecurityStateManager;
 import android.os.ISystemUpdateManager;
 import android.os.IThermalService;
 import android.os.IUserManager;
@@ -182,6 +185,7 @@
 import android.os.PermissionEnforcer;
 import android.os.PowerManager;
 import android.os.RecoverySystem;
+import android.os.SecurityStateManager;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.StatsFrameworkInitializer;
@@ -628,6 +632,17 @@
                         ctx.mMainThread.getHandler());
             }});
 
+        registerService(Context.SECURITY_STATE_SERVICE, SecurityStateManager.class,
+                new CachedServiceFetcher<SecurityStateManager>() {
+                    @Override
+                    public SecurityStateManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(
+                                Context.SECURITY_STATE_SERVICE);
+                        ISecurityStateManager service = ISecurityStateManager.Stub.asInterface(b);
+                        return new SecurityStateManager(service);
+                    }});
+
         registerService(Context.SENSOR_SERVICE, SensorManager.class,
                 new CachedServiceFetcher<SensorManager>() {
             @Override
@@ -960,6 +975,18 @@
                 return new TvInteractiveAppManager(service, ctx.getUserId());
             }});
 
+        registerService(Context.TV_AD_SERVICE, TvAdManager.class,
+                new CachedServiceFetcher<TvAdManager>() {
+                    @Override
+                    public TvAdManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder iBinder =
+                                ServiceManager.getServiceOrThrow(Context.TV_AD_SERVICE);
+                        ITvAdManager service =
+                                ITvAdManager.Stub.asInterface(iBinder);
+                        return new TvAdManager(service, ctx.getUserId());
+                    }});
+
         registerService(Context.TV_INPUT_SERVICE, TvInputManager.class,
                 new CachedServiceFetcher<TvInputManager>() {
             @Override
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 6a03c17..ce1d43d 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -180,6 +180,11 @@
 
     @Override
     public void injectInputEventToInputFilter(InputEvent event) throws RemoteException {
+        synchronized (mLock) {
+            throwIfCalledByNotTrustedUidLocked();
+            throwIfShutdownLocked();
+            throwIfNotConnectedLocked();
+        }
         mAccessibilityManager.injectInputEventToInputFilter(event);
     }
 
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index ebd5d64..cf19178 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -22,6 +22,7 @@
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEventsQuery;
 import android.content.pm.ParceledListSlice;
+import android.os.PersistableBundle;
 
 /**
  * System private API for talking with the UsageStatsManagerService.
@@ -77,6 +78,8 @@
             String callingPackage);
     void reportUsageStop(in IBinder activity, String token, String callingPackage);
     void reportUserInteraction(String packageName, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.REPORT_USAGE_STATS)")
+    void reportUserInteractionWithBundle(String packageName, int userId, in PersistableBundle eventExtras);
     int getUsageSource();
     void forceUsageSourceSettingRead();
     long getLastTimeAnyComponentUsed(String packageName, String callingPackage);
diff --git a/core/java/android/app/usage/ParcelableUsageEventList.java b/core/java/android/app/usage/ParcelableUsageEventList.java
index aa32392..7bc6cb9 100644
--- a/core/java/android/app/usage/ParcelableUsageEventList.java
+++ b/core/java/android/app/usage/ParcelableUsageEventList.java
@@ -223,6 +223,7 @@
         event.mContentAnnotations = null;
         event.mNotificationChannelId = null;
         event.mLocusId = null;
+        event.mExtras = null;
 
         switch (event.mEventType) {
             case Event.CONFIGURATION_CHANGE -> {
@@ -237,6 +238,11 @@
             case Event.STANDBY_BUCKET_CHANGED -> event.mBucketAndReason = in.readInt();
             case Event.NOTIFICATION_INTERRUPTION -> event.mNotificationChannelId = in.readString();
             case Event.LOCUS_ID_SET -> event.mLocusId = in.readString();
+            case Event.USER_INTERACTION -> {
+                if (in.readInt() != 0) {
+                    event.mExtras = in.readPersistableBundle(getClass().getClassLoader());
+                }
+            }
         }
         event.mFlags = in.readInt();
 
@@ -263,6 +269,14 @@
             case Event.STANDBY_BUCKET_CHANGED -> dest.writeInt(event.mBucketAndReason);
             case Event.NOTIFICATION_INTERRUPTION -> dest.writeString(event.mNotificationChannelId);
             case Event.LOCUS_ID_SET -> dest.writeString(event.mLocusId);
+            case Event.USER_INTERACTION -> {
+                if (event.mExtras != null) {
+                    dest.writeInt(1);
+                    dest.writePersistableBundle(event.mExtras);
+                } else {
+                    dest.writeInt(0);
+                }
+            }
         }
         dest.writeInt(event.mFlags);
     }
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 6c7eba0..9eb73b3 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -16,7 +16,9 @@
 package android.app.usage;
 
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -24,6 +26,7 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -551,6 +554,22 @@
         public int mLocusIdToken = UNASSIGNED_TOKEN;
 
         /** @hide */
+        public PersistableBundle mExtras = null;
+
+        /** @hide */
+        public static class UserInteractionEventExtrasToken {
+            public int mCategoryToken = UNASSIGNED_TOKEN;
+            public int mActionToken = UNASSIGNED_TOKEN;
+
+            public UserInteractionEventExtrasToken() {
+                // Do nothing.
+            }
+        }
+
+        /** @hide */
+        public UserInteractionEventExtrasToken mUserInteractionExtrasToken = null;
+
+        /** @hide */
         @EventFlags
         public int mFlags;
 
@@ -650,6 +669,21 @@
         }
 
         /**
+         * Retrieves a map of extended data from the event if the event is of type
+         * {@link #USER_INTERACTION}.
+         *
+         * @return the map of all extras that associated with the reported user interaction
+         *         event. The returned {@link PersistableBundle} will contain the extras
+         *         {@link UsageStatsManager#EXTRA_EVENT_CATEGORY} and
+         *         {@link UsageStatsManager#EXTRA_EVENT_ACTION}. {@link PersistableBundle#EMPTY}
+         *         will be returned if the details are not available.
+         */
+        @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API)
+        public @NonNull PersistableBundle getExtras() {
+            return mExtras == null ? PersistableBundle.EMPTY : mExtras;
+        }
+
+        /**
          * Returns a {@link Configuration} for this event if the event is of type
          * {@link #CONFIGURATION_CHANGE}, otherwise it returns null.
          */
@@ -747,6 +781,7 @@
             mBucketAndReason = orig.mBucketAndReason;
             mNotificationChannelId = orig.mNotificationChannelId;
             mLocusId = orig.mLocusId;
+            mExtras = orig.mExtras;
         }
     }
 
@@ -802,6 +837,7 @@
         if (mEventCount != mEventsToWrite.size()) {
             Log.w(TAG, "Partial usage event list received: " + mEventCount + " != "
                     + mEventsToWrite.size());
+            mEventCount = mEventsToWrite.size();
         }
     }
 
@@ -987,6 +1023,14 @@
             case Event.LOCUS_ID_SET:
                 p.writeString(event.mLocusId);
                 break;
+            case Event.USER_INTERACTION:
+                if (event.mExtras != null) {
+                    p.writeInt(1);
+                    p.writePersistableBundle(event.mExtras);
+                } else {
+                    p.writeInt(0);
+                }
+                break;
         }
         p.writeInt(event.mFlags);
     }
@@ -1036,6 +1080,7 @@
         eventOut.mContentAnnotations = null;
         eventOut.mNotificationChannelId = null;
         eventOut.mLocusId = null;
+        eventOut.mExtras = null;
 
         switch (eventOut.mEventType) {
             case Event.CONFIGURATION_CHANGE:
@@ -1059,6 +1104,11 @@
             case Event.LOCUS_ID_SET:
                 eventOut.mLocusId = p.readString();
                 break;
+            case Event.USER_INTERACTION:
+                if (p.readInt() != 0) {
+                    eventOut.mExtras = p.readPersistableBundle(getClass().getClassLoader());
+                }
+                break;
         }
         eventOut.mFlags = p.readInt();
     }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 4f1c993..85d223d 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -28,6 +28,7 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UserHandleAware;
+import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
@@ -35,6 +36,7 @@
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.Build;
+import android.os.PersistableBundle;
 import android.os.PowerWhitelistManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -392,6 +394,23 @@
     @SystemApi
     public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
 
+    /**
+     * A String extra, when used with {@link UsageEvents.Event#getExtras}, that indicates
+     * the category of the user interaction associated with the event. The category cannot
+     * be more than 127 characters, longer value will be truncated to 127 characters.
+     */
+    @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API)
+    public static final String EXTRA_EVENT_CATEGORY =
+            "android.app.usage.extra.EVENT_CATEGORY";
+
+    /**
+     * A String extra, when used with {@link UsageEvents.Event#getExtras}, that indicates
+     * the action of the user interaction associated with the event. The action cannot be
+     * more than 127 characters, longer value will be truncated to 127 characters.
+     */
+    @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API)
+    public static final String EXTRA_EVENT_ACTION =
+            "android.app.usage.extra.EVENT_ACTION";
 
     /**
      * App usage observers will consider the task root package the source of usage.
@@ -562,10 +581,10 @@
      * then {@code null} will be returned.</em>
      *
      * @param beginTime The inclusive beginning of the range of events to include in the results.
-     *                 Defined in terms of "Unix time", see
-     *                 {@link java.lang.System#currentTimeMillis}.
+     *                  Defined in terms of "Unix time", see
+     *                  {@link java.lang.System#currentTimeMillis}.
      * @param endTime The exclusive end of the range of events to include in the results. Defined
-     *               in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}.
+     *                in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}.
      * @return A {@link UsageEvents}.
      */
     public UsageEvents queryEvents(long beginTime, long endTime) {
@@ -611,10 +630,10 @@
      * then {@code null} will be returned.</em>
      *
      * @param beginTime The inclusive beginning of the range of events to include in the results.
-     *                 Defined in terms of "Unix time", see
-     *                 {@link java.lang.System#currentTimeMillis}.
+     *                  Defined in terms of "Unix time", see
+     *                  {@link java.lang.System#currentTimeMillis}.
      * @param endTime The exclusive end of the range of events to include in the results. Defined
-     *               in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}.
+     *                in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}.
      * @return A {@link UsageEvents} object.
      *
      * @see #queryEvents(long, long)
@@ -1128,6 +1147,7 @@
      * Reports user interaction with a given package in the given user.
      *
      * <p><em>This method is only for use by the system</em>
+     *
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.REPORT_USAGE_STATS)
@@ -1140,6 +1160,38 @@
     }
 
     /**
+     * Reports user interaction with given package and a particular {@code extras}
+     * in the given user.
+     *
+     * <p>
+     * Note: The structure of {@code extras} is a {@link PersistableBundle} with the
+     * category {@link #EXTRA_EVENT_CATEGORY} and the action {@link #EXTRA_EVENT_ACTION}.
+     * Category provides additional detail about the user interaction, the value
+     * is defined in namespace based. Example: android.app.notification could be used to
+     * indicate that the reported user interaction is related to notification. Action
+     * indicates the general action that performed.
+     * </p>
+     *
+     * @param packageName The package name of the app
+     * @param userId The user id who triggers the user interaction
+     * @param extras The {@link PersistableBundle} that will be used to specify the
+     *               extra details for the user interaction event. The {@link PersistableBundle}
+     *               must contain the extras {@link #EXTRA_EVENT_CATEGORY},
+     *               {@link #EXTRA_EVENT_ACTION}. Cannot be empty.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_USER_INTERACTION_TYPE_API)
+    @RequiresPermission(android.Manifest.permission.REPORT_USAGE_STATS)
+    public void reportUserInteraction(@NonNull String packageName, @UserIdInt int userId,
+            @NonNull PersistableBundle extras) {
+        try {
+            mService.reportUserInteractionWithBundle(packageName, userId, extras);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Report usage associated with a particular {@code token} has started. Tokens are app defined
      * strings used to represent usage of in-app features. Apps with the {@link
      * android.Manifest.permission#OBSERVE_APP_USAGE} permission can register time limit observers
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index c6012bb..6e45147 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.FunctionalUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -557,11 +558,45 @@
                             }
                         }).toArray(ComponentName[]::new));
             } catch (Exception e) {
-                Log.e(TAG, "Nofity service of inheritance info", e);
+                Log.e(TAG, "Notify service of inheritance info", e);
             }
         });
     }
 
+    private void tryAdapterConversion(
+            FunctionalUtils.RemoteExceptionIgnoringConsumer<RemoteViews> action,
+            RemoteViews original, String failureMsg) {
+        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
+                && (mHasPostedLegacyLists = mHasPostedLegacyLists
+                        || (original != null && original.hasLegacyLists()));
+
+        if (isConvertingAdapter) {
+            final RemoteViews viewsCopy = new RemoteViews(original);
+            Runnable updateWidgetWithTask = () -> {
+                try {
+                    viewsCopy.collectAllIntents().get();
+                    action.acceptOrThrow(viewsCopy);
+                } catch (Exception e) {
+                    Log.e(TAG, failureMsg, e);
+                }
+            };
+
+            if (Looper.getMainLooper() == Looper.myLooper()) {
+                createUpdateExecutorIfNull().execute(updateWidgetWithTask);
+                return;
+            }
+
+            updateWidgetWithTask.run();
+            return;
+        }
+
+        try {
+            action.acceptOrThrow(original);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Set the RemoteViews to use for the specified appWidgetIds.
      * <p>
@@ -586,32 +621,8 @@
             return;
         }
 
-        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
-                && (mHasPostedLegacyLists = mHasPostedLegacyLists
-                        || (views != null && views.hasLegacyLists()));
-
-        if (isConvertingAdapter) {
-            views.collectAllIntents();
-
-            if (Looper.getMainLooper() == Looper.myLooper()) {
-                RemoteViews viewsCopy = new RemoteViews(views);
-                createUpdateExecutorIfNull().execute(() -> {
-                    try {
-                        mService.updateAppWidgetIds(mPackageName, appWidgetIds, viewsCopy);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error updating app widget views in background", e);
-                    }
-                });
-
-                return;
-            }
-        }
-
-        try {
-            mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        tryAdapterConversion(view -> mService.updateAppWidgetIds(mPackageName, appWidgetIds,
+                view), views, "Error updating app widget views in background");
     }
 
     /**
@@ -716,32 +727,9 @@
             return;
         }
 
-        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
-                && (mHasPostedLegacyLists = mHasPostedLegacyLists
-                        || (views != null && views.hasLegacyLists()));
-
-        if (isConvertingAdapter) {
-            views.collectAllIntents();
-
-            if (Looper.getMainLooper() == Looper.myLooper()) {
-                RemoteViews viewsCopy = new RemoteViews(views);
-                createUpdateExecutorIfNull().execute(() -> {
-                    try {
-                        mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, viewsCopy);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error partially updating app widget views in background", e);
-                    }
-                });
-
-                return;
-            }
-        }
-
-        try {
-            mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        tryAdapterConversion(view -> mService.partiallyUpdateAppWidgetIds(mPackageName,
+                appWidgetIds, view), views,
+                "Error partially updating app widget views in background");
     }
 
     /**
@@ -793,33 +781,8 @@
             return;
         }
 
-        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
-                && (mHasPostedLegacyLists = mHasPostedLegacyLists
-                        || (views != null && views.hasLegacyLists()));
-
-        if (isConvertingAdapter) {
-            views.collectAllIntents();
-
-            if (Looper.getMainLooper() == Looper.myLooper()) {
-                RemoteViews viewsCopy = new RemoteViews(views);
-                createUpdateExecutorIfNull().execute(() -> {
-                    try {
-                        mService.updateAppWidgetProvider(provider, viewsCopy);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error updating app widget view using provider in background",
-                                e);
-                    }
-                });
-
-                return;
-            }
-        }
-
-        try {
-            mService.updateAppWidgetProvider(provider, views);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        tryAdapterConversion(view -> mService.updateAppWidgetProvider(provider, view), views,
+                "Error updating app widget view using provider in background");
     }
 
     /**
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 102cbf3..3520c0b 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -246,4 +246,10 @@
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void unregisterVirtualCamera(in VirtualCameraConfig camera);
+
+    /**
+     * Returns the id of the virtual camera with given config.
+     */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    int getVirtualCameraId(in VirtualCameraConfig camera);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index da8277c..9492a62 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -19,6 +19,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.companion.virtual.audio.VirtualAudioDevice;
@@ -340,12 +341,17 @@
         return mVirtualAudioDevice;
     }
 
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @NonNull
     VirtualCamera createVirtualCamera(@NonNull VirtualCameraConfig config) {
-        return new VirtualCamera(mVirtualDevice, config);
+        try {
+            mVirtualDevice.registerVirtualCamera(config);
+            return new VirtualCamera(mVirtualDevice, config);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
-    @NonNull
     void setShowPointerIcon(boolean showPointerIcon) {
         try {
             mVirtualDevice.setShowPointerIcon(showPointerIcon);
diff --git a/core/java/android/companion/virtual/camera/VirtualCamera.java b/core/java/android/companion/virtual/camera/VirtualCamera.java
index beee86f..52afa4e 100644
--- a/core/java/android/companion/virtual/camera/VirtualCamera.java
+++ b/core/java/android/companion/virtual/camera/VirtualCamera.java
@@ -66,15 +66,8 @@
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public VirtualCamera(
             @NonNull IVirtualDevice virtualDevice, @NonNull VirtualCameraConfig config) {
-        mVirtualDevice = virtualDevice;
+        mVirtualDevice = Objects.requireNonNull(virtualDevice);
         mConfig = Objects.requireNonNull(config);
-        Objects.requireNonNull(virtualDevice);
-        // TODO(b/310857519): Avoid registration inside constructor.
-        try {
-            mVirtualDevice.registerVirtualCamera(config);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
     }
 
     /** Returns the configuration of this virtual camera instance. */
@@ -83,6 +76,20 @@
         return mConfig;
     }
 
+    /**
+     * Returns the id of this virtual camera instance.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    @NonNull
+    public String getId() {
+        try {
+            return Integer.toString(mVirtualDevice.getVirtualCameraId(mConfig));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @Override
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void close() {
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 4b2cee6..697c25c 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -730,10 +730,7 @@
 
         /**
          * The next app to receive the permission protected data.
-         *
-         * @deprecated Use {@link #setNextAttributionSource} instead.
          */
-        @Deprecated
         public @NonNull Builder setNext(@Nullable AttributionSource value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x20;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1c6c7b5..b75c64d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -72,6 +72,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
+import android.os.Flags;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.IBinder;
@@ -4214,6 +4215,7 @@
             DEVICE_LOCK_SERVICE,
             VIRTUALIZATION_SERVICE,
             GRAMMATICAL_INFLECTION_SERVICE,
+            SECURITY_STATE_SERVICE,
 
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -5818,6 +5820,17 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.media.tv.ad.TvAdManager} for interacting with TV client-side advertisement
+     * services on the device.
+     *
+     * @see #getSystemService(String)
+     * @see android.media.tv.ad.TvAdManager
+     */
+    @FlaggedApi(android.media.tv.flags.Flags.FLAG_ENABLE_AD_SERVICE_FW)
+    public static final String TV_AD_SERVICE = "tv_ad";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.media.tv.TunerResourceManager} for interacting with TV
      * tuner resources on the device.
      *
@@ -6478,6 +6491,16 @@
     public static final String SHARED_CONNECTIVITY_SERVICE = "shared_connectivity";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.os.SecurityStateManager} for accessing the security state manager service.
+     *
+     * @see #getSystemService(String)
+     * @see android.os.SecurityStateManager
+     */
+    @FlaggedApi(Flags.FLAG_SECURITY_STATE_SERVICE)
+    public static final String SECURITY_STATE_SERVICE = "security_state";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c7a86fb..38bcfa2 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5353,11 +5353,11 @@
      * Broadcast Action: Sent to the responsible installer of an archived package when unarchival
      * is requested.
      *
-     * @see android.content.pm.PackageInstaller#requestUnarchive(String)
-     * @hide
+     * @see android.content.pm.PackageInstaller#requestUnarchive
      */
-    @SystemApi
     @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+    @BroadcastBehavior(explicitOnly = true)
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
 
     // ---------------------------------------------------------------------
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 1cfdb8b..5736a6d 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -20,7 +20,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -522,9 +521,7 @@
     /**
      * Returns the time at which the app was archived for the user.  Units are as
      * per {@link System#currentTimeMillis()}.
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public @CurrentTimeMillisLong long getArchiveTimeMillis() {
         return mArchiveTimeMillis;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 6df1f60..d35c392 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -354,10 +354,7 @@
     /**
      * Extra field for the package name of a package that is requested to be unarchived. Sent as
      * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent.
-     *
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final String EXTRA_UNARCHIVE_PACKAGE_NAME =
             "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
@@ -366,22 +363,16 @@
      * Extra field for the unarchive ID. Sent as
      * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent.
      *
-     * @see Session#setUnarchiveId(int)
-     *
-     * @hide
+     * @see SessionParams#setUnarchiveId
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final String EXTRA_UNARCHIVE_ID =
             "android.content.pm.extra.UNARCHIVE_ID";
 
     /**
      * If true, the requestor of the unarchival has specified that the app should be unarchived
-     * for {@link android.os.UserHandle#ALL}.
-     *
-     * @hide
+     * for all users.
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final String EXTRA_UNARCHIVE_ALL_USERS =
             "android.content.pm.extra.UNARCHIVE_ALL_USERS";
@@ -398,9 +389,7 @@
      * failure dialog.
      *
      * @see #requestUnarchive
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
 
@@ -675,10 +664,7 @@
      *
      * <p> Note that this does not mean that the unarchival has completed. This status should be
      * sent before any longer asynchronous action (e.g. app download) is started.
-     *
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final int UNARCHIVAL_OK = 0;
 
@@ -687,10 +673,7 @@
      *
      * <p> An example use case for this could be that the user needs to login to allow the
      * download for a paid app.
-     *
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1;
 
@@ -700,19 +683,13 @@
      * <p> The installer can optionally provide a {@code userActionIntent} for a space-clearing
      * dialog. If no action is provided, then a generic intent
      * {@link android.os.storage.StorageManager#ACTION_MANAGE_STORAGE} is started instead.
-     *
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2;
 
     /**
      * The device is not connected to the internet
-     *
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3;
 
@@ -720,10 +697,7 @@
      * The installer responsible for the unarchival is disabled.
      *
      * <p> Should only be used by the system.
-     *
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4;
 
@@ -731,19 +705,13 @@
      * The installer responsible for the unarchival has been uninstalled
      *
      * <p> Should only be used by the system.
-     *
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5;
 
     /**
      * Generic error: The app cannot be unarchived.
-     *
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public static final int UNARCHIVAL_GENERIC_ERROR = 100;
 
@@ -2364,12 +2332,10 @@
      * @param statusReceiver Callback used to notify when the operation is completed.
      * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
      *                                              available to the caller or isn't archived.
-     * @hide
      */
     @RequiresPermission(anyOf = {
             Manifest.permission.DELETE_PACKAGES,
             Manifest.permission.REQUEST_DELETE_PACKAGES})
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
             throws PackageManager.NameNotFoundException {
@@ -2395,19 +2361,16 @@
      *
      * @param statusReceiver Callback used to notify whether the installer has accepted the
      *                       unarchival request or an error has occurred. The status update will be
-     *                       sent though {@link EXTRA_UNARCHIVE_STATUS}. Only one status will be
+     *                       sent though {@link #EXTRA_UNARCHIVE_STATUS}. Only one status will be
      *                       sent.
      * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
      *                                              visible to the caller or if the package has no
      *                                              installer on the device anymore to unarchive it.
      * @throws IOException If parameters were unsatisfiable, such as lack of disk space.
-     *
-     * @hide
      */
     @RequiresPermission(anyOf = {
             Manifest.permission.INSTALL_PACKAGES,
             Manifest.permission.REQUEST_INSTALL_PACKAGES})
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public void requestUnarchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
             throws IOException, PackageManager.NameNotFoundException {
@@ -2435,12 +2398,10 @@
      * @param userActionIntent     Optional intent to start a follow up action required to
      *                             facilitate the unarchival flow (e.g. user needs to log in).
      * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists
-     * @hide
      */
     @RequiresPermission(anyOf = {
             Manifest.permission.INSTALL_PACKAGES,
             Manifest.permission.REQUEST_INSTALL_PACKAGES})
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public void reportUnarchivalStatus(int unarchiveId, @UnarchivalStatus int status,
             long requiredStorageBytes, @Nullable PendingIntent userActionIntent)
@@ -3454,11 +3415,8 @@
          * <p> The ID should be retrieved from the unarchive intent and passed into the
          * session that's being created to unarchive the app in question. Used to link the unarchive
          * intent and the install session to disambiguate.
-         *
-         * @hide
          */
         @FlaggedApi(Flags.FLAG_ARCHIVING)
-        @SystemApi
         public void setUnarchiveId(int unarchiveId) {
             this.unarchiveId = unarchiveId;
         }
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index c7091ad..70e6f98 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -177,12 +177,10 @@
     /**
      * Whether the package is currently in an archived state.
      *
-     * <p>Packages can be archived through {@link PackageArchiver} and do not have any APKs stored
-     * on the device, but do keep the data directory.
-     * @hide
+     * <p>Packages can be archived through {@link PackageInstaller#requestArchive} and do not have
+     * any APKs stored on the device, but do keep the data directory.
+     *
      */
-    // TODO(b/278553670) Unhide and update @links before launch.
-    @SystemApi
     @FlaggedApi(Flags.FLAG_ARCHIVING)
     public boolean isArchived;
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6775f9b..a22fe3f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1263,18 +1263,14 @@
 
     /**
      * Flag parameter to also retrieve some information about archived packages.
-     * Packages can be archived through
-     * {@link PackageInstaller#requestArchive(String, IntentSender)} and do not have any APKs stored
-     * on the device, but do keep the data directory.
+     * Packages can be archived through {@link PackageInstaller#requestArchive} and do not have any
+     * APKs stored on the device, but do keep the data directory.
      * <p> Note: Archived apps are a subset of apps returned by {@link #MATCH_UNINSTALLED_PACKAGES}.
      * <p> Note: this flag may cause less information about currently installed
      * applications to be returned.
      * <p> Note: use of this flag requires the android.permission.QUERY_ALL_PACKAGES
      * permission to see uninstalled packages.
-     * @hide
      */
-    // TODO(b/278553670) Unhide and update @links before launch.
-    @SystemApi
     @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
     public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;
 
@@ -8969,10 +8965,7 @@
      *
      * @throws NameNotFoundException if the given package name is not available to the caller.
      * @see PackageInstaller#requestArchive(String, IntentSender)
-     *
-     * @hide
      */
-    @SystemApi
     @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
     public boolean isAppArchivable(@NonNull String packageName) throws NameNotFoundException {
         throw new UnsupportedOperationException("isAppArchivable not implemented");
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index a565f68..d21b818 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -94,3 +94,10 @@
     description: "Feature flag to read install related information from an APK."
     bug: "275658500"
 }
+
+flag {
+    name: "use_pia_v2"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable the refactored Package Installer app with updated UI."
+    bug: "182205982"
+}
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 1b8eb07..40592a1 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -15,3 +15,12 @@
     description: "Feature flag for passing in an AssetFileDescriptor to create an frro"
     bug: "304478666"
 }
+
+flag {
+    name: "manifest_flagging"
+    namespace: "resource_manager"
+    description: "Feature flag for flagging manifest entries"
+    bug: "297373084"
+    # This flag is read in PackageParser at boot time, and in aapt2 which is a build tool.
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index d786d9a..18c95bfb 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -66,6 +66,13 @@
             + ".extra.sensor";
 
     /**
+     * An extra containing the notification id that triggered the intent
+     * @hide
+     */
+    public static final String EXTRA_NOTIFICATION_ID = SensorPrivacyManager.class.getName()
+            + ".extra.notification_id";
+
+    /**
      * An extra indicating if all sensors are affected
      * @hide
      */
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index dfc27ca..507e814 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -16,6 +16,7 @@
 
 package android.hardware.camera2;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -24,6 +25,8 @@
 import android.hardware.camera2.impl.SyntheticKey;
 import android.util.Log;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
diff --git a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
index d4ce0eb..5cbb0bb 100644
--- a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
@@ -16,8 +16,6 @@
 
 package android.hardware.camera2.params;
 
-import static com.android.internal.R.string.hardware;
-
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index ed31002..eabe13b 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -123,6 +123,12 @@
 
     private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000;
 
+    private static final int[] UID_USAGE_TIME_PROCESS_STATES = {
+            BatteryConsumer.PROCESS_STATE_FOREGROUND,
+            BatteryConsumer.PROCESS_STATE_BACKGROUND,
+            BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE
+    };
+
     private final int mDischargePercentage;
     private final double mBatteryCapacityMah;
     private final long mStatsStartTimestampMs;
@@ -516,6 +522,22 @@
             proto.write(
                     BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_BACKGROUND_MILLIS,
                     bgMs);
+            for (int processState : UID_USAGE_TIME_PROCESS_STATES) {
+                final long timeInStateMillis = consumer.getTimeInProcessStateMs(processState);
+                if (timeInStateMillis <= 0) {
+                    continue;
+                }
+                final long timeInStateToken = proto.start(
+                        BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_STATE);
+                proto.write(
+                        BatteryUsageStatsAtomsProto.UidBatteryConsumer.TimeInState.PROCESS_STATE,
+                        processState);
+                proto.write(
+                        BatteryUsageStatsAtomsProto.UidBatteryConsumer.TimeInState
+                                .TIME_IN_STATE_MILLIS,
+                        timeInStateMillis);
+                proto.end(timeInStateToken);
+            }
             proto.end(token);
 
             if (proto.getRawSize() >= maxRawSize) {
diff --git a/core/java/android/os/ISecurityStateManager.aidl b/core/java/android/os/ISecurityStateManager.aidl
new file mode 100644
index 0000000..8ae624d
--- /dev/null
+++ b/core/java/android/os/ISecurityStateManager.aidl
@@ -0,0 +1,26 @@
+/* //device/java/android/android/os/ISecurityStateManager.aidl
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+/** @hide */
+interface ISecurityStateManager {
+    Bundle getGlobalSecurityState();
+}
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index da647e2..161951e 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -437,6 +438,7 @@
      * @see #getData()
      * @see #setData(Bundle)
      */
+    @Nullable
     public Bundle peekData() {
         return data;
     }
diff --git a/core/java/android/os/SecurityStateManager.java b/core/java/android/os/SecurityStateManager.java
new file mode 100644
index 0000000..4fa61e0
--- /dev/null
+++ b/core/java/android/os/SecurityStateManager.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.content.Context;
+
+/**
+ * SecurityStateManager provides the functionality to query the security status of the system and
+ * platform components. For example, this includes the system and vendor security patch level.
+ */
+@FlaggedApi(Flags.FLAG_SECURITY_STATE_SERVICE)
+@SystemService(Context.SECURITY_STATE_SERVICE)
+public class SecurityStateManager {
+
+    /**
+     * The system SPL key returned as part of the {@code Bundle} from
+     * {@code getGlobalSecurityState}.
+     */
+    public static final String KEY_SYSTEM_SPL = "system_spl";
+
+    /**
+     * The vendor SPL key returned as part of the {@code Bundle} from
+     * {@code getGlobalSecurityState}.
+     */
+    public static final String KEY_VENDOR_SPL = "vendor_spl";
+
+    /**
+     * The kernel version key returned as part of the {@code Bundle} from
+     * {@code getGlobalSecurityState}.
+     */
+    public static final String KEY_KERNEL_VERSION = "kernel_version";
+
+    private final ISecurityStateManager mService;
+
+    /**
+     * @hide
+     */
+    public SecurityStateManager(ISecurityStateManager service) {
+        mService = requireNonNull(service, "missing ISecurityStateManager");
+    }
+
+    /**
+     * Returns the current global security state. Each key-value pair is a mapping of a component
+     * of the global security state to its current version/SPL (security patch level). For example,
+     * the {@code KEY_SYSTEM_SPL} key will map to the SPL of the system as defined in
+     * {@link android.os.Build.VERSION}. The bundle will also include mappings from WebView packages
+     * and packages listed under config {@code config_securityStatePackages} to their respective
+     * versions as defined in {@link android.content.pm.PackageInfo#versionName}.
+     *
+     * @return A {@code Bundle} that contains the global security state information as
+     * string-to-string key-value pairs.
+     */
+    @FlaggedApi(Flags.FLAG_SECURITY_STATE_SERVICE)
+    @NonNull
+    public Bundle getGlobalSecurityState() {
+        try {
+            return mService.getGlobalSecurityState();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index a78f221..c085f33 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -57,6 +57,13 @@
 }
 
 flag {
+    name: "security_state_service"
+    namespace: "dynamic_spl"
+    description: "Guards the Security State API."
+    bug: "302189431"
+}
+
+flag {
     name: "battery_saver_supported_check_api"
     namespace: "backstage_power"
     description: "Guards a new API in PowerManager to check if battery saver is supported or not."
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 6853892..78a12f7 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1738,23 +1738,6 @@
         return RoSystemProperties.CRYPTO_FILE_ENCRYPTED;
     }
 
-    /** {@hide}
-     * @deprecated Use {@link #isFileEncrypted} instead, since emulated FBE is no longer supported.
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Deprecated
-    public static boolean isFileEncryptedNativeOnly() {
-        return isFileEncrypted();
-    }
-
-    /** {@hide}
-     * @deprecated Use {@link #isFileEncrypted} instead, since emulated FBE is no longer supported.
-     */
-    @Deprecated
-    public static boolean isFileEncryptedNativeOrEmulated() {
-        return isFileEncrypted();
-    }
-
     /** {@hide} */
     public static boolean hasAdoptable() {
         switch (SystemProperties.get(PROP_ADOPTABLE)) {
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index dc86e3f5..5cbc18e 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -56,4 +56,11 @@
   namespace: "permissions"
   description: "enables logging of the OP_ENABLE_MOBILE_DATA_BY_USER"
   bug: "222650148"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "factory_reset_prep_permission_apis"
+  namespace: "wallet_integration"
+  description: "enable Permission PREPARE_FACTORY_RESET."
+  bug: "302016478"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ff6ec29..8f18c5f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14975,6 +14975,38 @@
         public static final String APP_OPS_CONSTANTS = "app_ops_constants";
 
         /**
+         * Device Idle (Doze) specific settings.
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "inactive_to=60000,sensing_to=400000"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * inactive_to                      (long)
+         * sensing_to                       (long)
+         * motion_inactive_to               (long)
+         * idle_after_inactive_to           (long)
+         * idle_pending_to                  (long)
+         * max_idle_pending_to              (long)
+         * idle_pending_factor              (float)
+         * quick_doze_delay_to              (long)
+         * idle_to                          (long)
+         * max_idle_to                      (long)
+         * idle_factor                      (float)
+         * min_time_to_alarm                (long)
+         * max_temp_app_whitelist_duration  (long)
+         * notification_whitelist_duration  (long)
+         * </pre>
+         *
+         * <p>
+         * Type: string
+         * @hide
+         * @see com.android.server.DeviceIdleController.Constants
+         */
+        public static final String DEVICE_IDLE_CONSTANTS = "device_idle_constants";
+
+        /**
          * Battery Saver specific settings
          * This is encoded as a key=value list, separated by commas. Ex:
          *
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0063d13..886727e 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -490,16 +490,32 @@
     /**
      * Notify changes to activity state changes on certain subscription.
      *
+     * @param subId for which data activity state changed.
+     * @param dataActivityType indicates the latest data activity type e.g. {@link
+     * TelephonyManager#DATA_ACTIVITY_IN}
+     */
+    public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) {
+        try {
+            sRegistry.notifyDataActivityForSubscriber(subId, dataActivityType);
+        } catch (RemoteException ex) {
+            // system process is dead
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Notify changes to activity state changes on certain subscription.
+     *
      * @param slotIndex for which data activity changed. Can be derived from subId except
      * when subId is invalid.
      * @param subId for which data activity state changed.
-     * @param dataActivityType indicates the latest data activity type e.g, {@link
+     * @param dataActivityType indicates the latest data activity type e.g. {@link
      * TelephonyManager#DATA_ACTIVITY_IN}
      */
     public void notifyDataActivityChanged(int slotIndex, int subId,
             @DataActivityType int dataActivityType) {
         try {
-            sRegistry.notifyDataActivityForSubscriber(slotIndex, subId, dataActivityType);
+            sRegistry.notifyDataActivityForSubscriberWithSlot(slotIndex, subId, dataActivityType);
         } catch (RemoteException ex) {
             // system process is dead
             throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 23d0de3..cbbe785 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -503,9 +503,6 @@
     // be dumped as additional context
     private static volatile boolean sDebugUsageAfterRelease = false;
 
-    static GlobalTransactionWrapper sGlobalTransaction;
-    static long sTransactionNestCount = 0;
-
     private static final NativeAllocationRegistry sRegistry =
             NativeAllocationRegistry.createMalloced(SurfaceControl.class.getClassLoader(),
                     nativeGetNativeSurfaceControlFinalizer());
@@ -1590,54 +1587,30 @@
         return mNativeObject != 0;
     }
 
-    /*
-     * set surface parameters.
-     * needs to be inside open/closeTransaction block
-     */
-
     /** start a transaction
      * @hide
-     */
-    @UnsupportedAppUsage
-    public static void openTransaction() {
-        synchronized (SurfaceControl.class) {
-            if (sGlobalTransaction == null) {
-                sGlobalTransaction = new GlobalTransactionWrapper();
-            }
-            synchronized(SurfaceControl.class) {
-                sTransactionNestCount++;
-            }
-        }
-    }
-
-    /**
-     * Merge the supplied transaction in to the deprecated "global" transaction.
-     * This clears the supplied transaction in an identical fashion to {@link Transaction#merge}.
-     * <p>
-     * This is a utility for interop with legacy-code and will go away with the Global Transaction.
-     * @hide
+     * @deprecated Use regular Transaction instead.
      */
     @Deprecated
-    public static void mergeToGlobalTransaction(Transaction t) {
-        synchronized(SurfaceControl.class) {
-            sGlobalTransaction.merge(t);
-        }
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+            publicAlternatives = "Use {@code SurfaceControl.Transaction} instead",
+            trackingBug = 247078497)
+    public static void openTransaction() {
+        // TODO(b/247078497): It was used for global transaction (all usages are removed).
+        //  Keep the method declaration to avoid breaking reference from legacy access.
     }
 
     /** end a transaction
      * @hide
+     * @deprecated Use regular Transaction instead.
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+            publicAlternatives = "Use {@code SurfaceControl.Transaction} instead",
+            trackingBug = 247078497)
     public static void closeTransaction() {
-        synchronized(SurfaceControl.class) {
-            if (sTransactionNestCount == 0) {
-                Log.e(TAG,
-                        "Call to SurfaceControl.closeTransaction without matching openTransaction");
-            } else if (--sTransactionNestCount > 0) {
-                return;
-            }
-            sGlobalTransaction.applyGlobalTransaction(false);
-        }
+        // TODO(b/247078497): It was used for global transaction (all usages are removed).
+        //  Keep the method declaration to avoid breaking reference from legacy access.
     }
 
     /**
@@ -4513,39 +4486,6 @@
     }
 
     /**
-     * As part of eliminating usage of the global Transaction we expose
-     * a SurfaceControl.getGlobalTransaction function. However calling
-     * apply on this global transaction (rather than using closeTransaction)
-     * would be very dangerous. So for the global transaction we use this
-     * subclass of Transaction where the normal apply throws an exception.
-     */
-    private static class GlobalTransactionWrapper extends SurfaceControl.Transaction {
-        void applyGlobalTransaction(boolean sync) {
-            applyResizedSurfaces();
-            notifyReparentedSurfaces();
-            nativeApplyTransaction(mNativeObject, sync, /*oneWay*/ false);
-        }
-
-        @Override
-        public void apply(boolean sync) {
-            throw new RuntimeException("Global transaction must be applied from closeTransaction");
-        }
-    }
-
-    /**
-     * This is a refactoring utility function to enable lower levels of code to be refactored
-     * from using the global transaction (and instead use a passed in Transaction) without
-     * having to refactor the higher levels at the same time.
-     * The returned global transaction can't be applied, it must be applied from closeTransaction
-     * Unless you are working on removing Global Transaction usage in the WindowManager, this
-     * probably isn't a good function to use.
-     * @hide
-     */
-    public static Transaction getGlobalTransaction() {
-        return sGlobalTransaction;
-    }
-
-    /**
      * @hide
      */
     public void resize(int w, int h) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d591f89..d58c07d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -33018,7 +33018,7 @@
     }
 
     private float getSizePercentage() {
-        if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) {
+        if (mResources == null || getVisibility() != VISIBLE) {
             return 0;
         }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7a6c292..cac5387 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -991,7 +991,7 @@
     // for idleness handling.
     private boolean mHasIdledMessage = false;
     // time for touch boost period.
-    private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500;
+    private static final int FRAME_RATE_TOUCH_BOOST_TIME = 3000;
     // time for checking idle status periodically.
     private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500;
     // time for revaluating the idle status before lowering the frame rate.
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index a11c6d0..a404bd6 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -54,7 +54,7 @@
      * @param displayId the logical display id.
      * @param scale magnification scale.
      */
-    void setScale(int displayId, float scale);
+    void setScaleForWindowMagnification(int displayId, float scale);
 
      /**
      * Disables window magnification on specified display with animation.
diff --git a/core/java/android/webkit/IWebViewUpdateService.aidl b/core/java/android/webkit/IWebViewUpdateService.aidl
index e177731..c6bd20c 100644
--- a/core/java/android/webkit/IWebViewUpdateService.aidl
+++ b/core/java/android/webkit/IWebViewUpdateService.aidl
@@ -79,4 +79,9 @@
      * Used by Settings to enable/disable multiprocess.
      */
     void enableMultiProcess(boolean enable);
+
+    /**
+     * Used by Settings to get the default WebView package.
+     */
+    WebViewProviderInfo getDefaultWebViewPackage();
 }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a919c00..1acebf4 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1054,8 +1054,7 @@
     }
 
     private class SetRemoteCollectionItemListAdapterAction extends Action {
-        @NonNull
-        private CompletableFuture<RemoteCollectionItems> mItemsFuture;
+        private @Nullable RemoteCollectionItems mItems;
         final Intent mServiceIntent;
         int mIntentId = -1;
         boolean mIsReplacedIntoAction = false;
@@ -1064,92 +1063,46 @@
                 @NonNull RemoteCollectionItems items) {
             mViewId = id;
             items.setHierarchyRootData(getHierarchyRootData());
-            mItemsFuture = CompletableFuture.completedFuture(items);
+            mItems = items;
             mServiceIntent = null;
         }
 
         SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) {
             mViewId = id;
-            mItemsFuture = getItemsFutureFromIntentWithTimeout(intent);
-            setHierarchyRootData(getHierarchyRootData());
+            mItems = null;
             mServiceIntent = intent;
         }
 
-        private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
-                Intent intent) {
-            if (intent == null) {
-                Log.e(LOG_TAG, "Null intent received when generating adapter future");
-                return CompletableFuture.completedFuture(new RemoteCollectionItems
-                        .Builder().build());
-            }
-
-            final Context context = ActivityThread.currentApplication();
-            final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
-
-            context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
-                    result.defaultExecutor(), new ServiceConnection() {
-                        @Override
-                        public void onServiceConnected(ComponentName componentName,
-                                IBinder iBinder) {
-                            RemoteCollectionItems items;
-                            try {
-                                items = IRemoteViewsFactory.Stub.asInterface(iBinder)
-                                        .getRemoteCollectionItems();
-                            } catch (RemoteException re) {
-                                items = new RemoteCollectionItems.Builder().build();
-                                Log.e(LOG_TAG, "Error getting collection items from the factory",
-                                        re);
-                            } finally {
-                                context.unbindService(this);
-                            }
-
-                            result.complete(items);
-                        }
-
-                        @Override
-                        public void onServiceDisconnected(ComponentName componentName) { }
-                    });
-
-            result.completeOnTimeout(
-                    new RemoteCollectionItems.Builder().build(),
-                    MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS);
-
-            return result;
-        }
-
         SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
             mViewId = parcel.readInt();
             mIntentId = parcel.readInt();
-            mItemsFuture = CompletableFuture.completedFuture(mIntentId != -1
-                    ? null
-                    : new RemoteCollectionItems(parcel, getHierarchyRootData()));
             mServiceIntent = parcel.readTypedObject(Intent.CREATOR);
+            mItems = mServiceIntent != null
+                    ? null
+                    : new RemoteCollectionItems(parcel, getHierarchyRootData());
         }
 
         @Override
         public void setHierarchyRootData(HierarchyRootData rootData) {
-            if (mIntentId == -1) {
-                mItemsFuture = mItemsFuture
-                        .thenApply(rc -> {
-                            rc.setHierarchyRootData(rootData);
-                            return rc;
-                        });
+            if (mItems != null) {
+                mItems.setHierarchyRootData(rootData);
                 return;
             }
 
-            // Set the root data for items in the cache instead
-            mCollectionCache.setHierarchyDataForId(mIntentId, rootData);
+            if (mIntentId != -1) {
+                // Set the root data for items in the cache instead
+                mCollectionCache.setHierarchyDataForId(mIntentId, rootData);
+            }
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(mViewId);
             dest.writeInt(mIntentId);
-            if (mIntentId == -1) {
-                RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
-                items.writeToParcel(dest, flags, /* attached= */ true);
-            }
             dest.writeTypedObject(mServiceIntent, flags);
+            if (mItems != null) {
+                mItems.writeToParcel(dest, flags, /* attached= */ true);
+            }
         }
 
         @Override
@@ -1159,7 +1112,9 @@
             if (target == null) return;
 
             RemoteCollectionItems items = mIntentId == -1
-                    ? getCollectionItemsFromFuture(mItemsFuture)
+                    ? mItems == null
+                            ? new RemoteCollectionItems.Builder().build()
+                            : mItems
                     : mCollectionCache.getItemsForId(mIntentId);
 
             // Ensure that we are applying to an AppWidget root
@@ -1216,51 +1171,32 @@
 
         @Override
         public void visitUris(@NonNull Consumer<Uri> visitor) {
-            RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
-            items.visitUris(visitor);
-        }
-    }
+            if (mIntentId != -1 || mItems == null) {
+                return;
+            }
 
-    private static RemoteCollectionItems getCollectionItemsFromFuture(
-            CompletableFuture<RemoteCollectionItems> itemsFuture) {
-        RemoteCollectionItems items;
-        try {
-            items = itemsFuture.get();
-        } catch (Exception e) {
-            Log.e(LOG_TAG, "Error getting collection items from future", e);
-            items = new RemoteCollectionItems.Builder().build();
+            mItems.visitUris(visitor);
         }
-
-        return items;
     }
 
     /**
      * @hide
      */
-    public void collectAllIntents() {
-        mCollectionCache.collectAllIntentsNoComplete(this);
+    public CompletableFuture<Void> collectAllIntents() {
+        return mCollectionCache.collectAllIntentsNoComplete(this);
     }
 
     private class RemoteCollectionCache {
         private SparseArray<String> mIdToUriMapping = new SparseArray<>();
         private HashMap<String, RemoteCollectionItems> mUriToCollectionMapping = new HashMap<>();
 
-        // We don't put this into the parcel
-        private HashMap<String, CompletableFuture<RemoteCollectionItems>> mTempUriToFutureMapping =
-                new HashMap<>();
-
         RemoteCollectionCache() { }
 
         RemoteCollectionCache(RemoteCollectionCache src) {
-            boolean isWaitingCache = src.mTempUriToFutureMapping.size() != 0;
             for (int i = 0; i < src.mIdToUriMapping.size(); i++) {
                 String uri = src.mIdToUriMapping.valueAt(i);
                 mIdToUriMapping.put(src.mIdToUriMapping.keyAt(i), uri);
-                if (isWaitingCache) {
-                    mTempUriToFutureMapping.put(uri, src.mTempUriToFutureMapping.get(uri));
-                } else {
-                    mUriToCollectionMapping.put(uri, src.mUriToCollectionMapping.get(uri));
-                }
+                mUriToCollectionMapping.put(uri, src.mUriToCollectionMapping.get(uri));
             }
         }
 
@@ -1281,14 +1217,8 @@
 
         void setHierarchyDataForId(int intentId, HierarchyRootData data) {
             String uri = mIdToUriMapping.get(intentId);
-            if (mTempUriToFutureMapping.get(uri) != null) {
-                CompletableFuture<RemoteCollectionItems> itemsFuture =
-                        mTempUriToFutureMapping.get(uri);
-                mTempUriToFutureMapping.put(uri, itemsFuture.thenApply(rc -> {
-                    rc.setHierarchyRootData(data);
-                    return rc;
-                }));
-
+            if (mUriToCollectionMapping.get(uri) == null) {
+                Log.e(LOG_TAG, "Error setting hierarchy data for id=" + intentId);
                 return;
             }
 
@@ -1301,14 +1231,17 @@
             return mUriToCollectionMapping.get(uri);
         }
 
-        void collectAllIntentsNoComplete(@NonNull RemoteViews inViews) {
+        CompletableFuture<Void> collectAllIntentsNoComplete(@NonNull RemoteViews inViews) {
+            CompletableFuture<Void> collectionFuture = CompletableFuture.completedFuture(null);
             if (inViews.hasSizedRemoteViews()) {
                 for (RemoteViews remoteViews : inViews.mSizedRemoteViews) {
-                    remoteViews.collectAllIntents();
+                    collectionFuture = CompletableFuture.allOf(collectionFuture,
+                            collectAllIntentsNoComplete(remoteViews));
                 }
             } else if (inViews.hasLandscapeAndPortraitLayouts()) {
-                inViews.mLandscape.collectAllIntents();
-                inViews.mPortrait.collectAllIntents();
+                collectionFuture = CompletableFuture.allOf(
+                        collectAllIntentsNoComplete(inViews.mLandscape),
+                        collectAllIntentsNoComplete(inViews.mPortrait));
             } else if (inViews.mActions != null) {
                 for (Action action : inViews.mActions) {
                     if (action instanceof SetRemoteCollectionItemListAdapterAction rca) {
@@ -1318,40 +1251,95 @@
                         }
 
                         if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) {
-                            String uri = mIdToUriMapping.get(rca.mIntentId);
-                            mTempUriToFutureMapping.put(uri, rca.mItemsFuture);
-                            rca.mItemsFuture = CompletableFuture.completedFuture(null);
+                            final String uri = mIdToUriMapping.get(rca.mIntentId);
+                            collectionFuture = CompletableFuture.allOf(collectionFuture,
+                                    getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
+                                            .thenAccept(rc -> {
+                                                rc.setHierarchyRootData(getHierarchyRootData());
+                                                mUriToCollectionMapping.put(uri, rc);
+                                            }));
+                            rca.mItems = null;
                             continue;
                         }
 
                         // Differentiate between the normal collection actions and the ones with
                         // intents.
                         if (rca.mServiceIntent != null) {
-                            String uri = rca.mServiceIntent.toUri(0);
+                            final String uri = rca.mServiceIntent.toUri(0);
                             int index = mIdToUriMapping.indexOfValue(uri);
                             if (index == -1) {
                                 int newIntentId = mIdToUriMapping.size();
                                 rca.mIntentId = newIntentId;
                                 mIdToUriMapping.put(newIntentId, uri);
-                                // mUriToIntentMapping.put(uri, mServiceIntent);
-                                mTempUriToFutureMapping.put(uri, rca.mItemsFuture);
                             } else {
                                 rca.mIntentId = mIdToUriMapping.keyAt(index);
+                                rca.mItems = null;
+                                continue;
                             }
-                            rca.mItemsFuture = CompletableFuture.completedFuture(null);
+                            collectionFuture = CompletableFuture.allOf(collectionFuture,
+                                    getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
+                                            .thenAccept(rc -> {
+                                                rc.setHierarchyRootData(getHierarchyRootData());
+                                                mUriToCollectionMapping.put(uri, rc);
+                                            }));
+                            rca.mItems = null;
                         } else {
-                            RemoteCollectionItems items = getCollectionItemsFromFuture(
-                                    rca.mItemsFuture);
-                            for (RemoteViews views : items.mViews) {
-                                views.collectAllIntents();
+                            for (RemoteViews views : rca.mItems.mViews) {
+                                collectionFuture = CompletableFuture.allOf(collectionFuture,
+                                        collectAllIntentsNoComplete(views));
                             }
                         }
                     } else if (action instanceof ViewGroupActionAdd vgaa
                             && vgaa.mNestedViews != null) {
-                        vgaa.mNestedViews.collectAllIntents();
+                        collectionFuture = CompletableFuture.allOf(collectionFuture,
+                                collectAllIntentsNoComplete(vgaa.mNestedViews));
                     }
                 }
             }
+
+            return collectionFuture;
+        }
+
+        private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
+                Intent intent) {
+            if (intent == null) {
+                Log.e(LOG_TAG, "Null intent received when generating adapter future");
+                return CompletableFuture.completedFuture(new RemoteCollectionItems
+                    .Builder().build());
+            }
+
+            final Context context = ActivityThread.currentApplication();
+            final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
+
+            context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+                    result.defaultExecutor(), new ServiceConnection() {
+                        @Override
+                        public void onServiceConnected(ComponentName componentName,
+                                IBinder iBinder) {
+                            RemoteCollectionItems items;
+                            try {
+                                items = IRemoteViewsFactory.Stub.asInterface(iBinder)
+                                    .getRemoteCollectionItems();
+                            } catch (RemoteException re) {
+                                items = new RemoteCollectionItems.Builder().build();
+                                Log.e(LOG_TAG, "Error getting collection items from the factory",
+                                        re);
+                            } finally {
+                                context.unbindService(this);
+                            }
+
+                            result.complete(items);
+                        }
+
+                        @Override
+                        public void onServiceDisconnected(ComponentName componentName) { }
+                    });
+
+            result.completeOnTimeout(
+                    new RemoteCollectionItems.Builder().build(),
+                    MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS);
+
+            return result;
         }
 
         public void writeToParcel(Parcel out, int flags) {
@@ -1360,10 +1348,7 @@
                 out.writeInt(mIdToUriMapping.keyAt(i));
                 String intentUri = mIdToUriMapping.valueAt(i);
                 out.writeString8(intentUri);
-                RemoteCollectionItems items = mTempUriToFutureMapping.get(intentUri) != null
-                        ? getCollectionItemsFromFuture(mTempUriToFutureMapping.get(intentUri))
-                        : mUriToCollectionMapping.get(intentUri);
-                items.writeToParcel(out, flags, true);
+                mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true);
             }
         }
     }
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 4e0f9a5..0ec9ffe 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -108,6 +108,18 @@
      */
     public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13;
 
+    /**
+     * Creates a decor surface in the parent Task of the TaskFragment. The created decor surface
+     * will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED}
+     * event callback.
+     */
+    public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14;
+
+    /**
+     * Removes the decor surface in the parent Task of the TaskFragment.
+     */
+    public static final int OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE = 15;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -124,6 +136,8 @@
             OP_TYPE_SET_ISOLATED_NAVIGATION,
             OP_TYPE_REORDER_TO_BOTTOM_OF_TASK,
             OP_TYPE_REORDER_TO_TOP_OF_TASK,
+            OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE,
+            OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
index e6eeca4..a77c234 100644
--- a/core/java/android/window/TaskFragmentParentInfo.java
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -22,6 +22,9 @@
 import android.content.res.Configuration;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.SurfaceControl;
+
+import java.util.Objects;
 
 /**
  * The information about the parent Task of a particular TaskFragment
@@ -37,12 +40,15 @@
 
     private final boolean mHasDirectActivity;
 
+    @Nullable private final SurfaceControl mDecorSurface;
+
     public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
-            boolean visible, boolean hasDirectActivity) {
+            boolean visible, boolean hasDirectActivity, @Nullable SurfaceControl decorSurface) {
         mConfiguration.setTo(configuration);
         mDisplayId = displayId;
         mVisible = visible;
         mHasDirectActivity = hasDirectActivity;
+        mDecorSurface = decorSurface;
     }
 
     public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
@@ -50,6 +56,7 @@
         mDisplayId = info.mDisplayId;
         mVisible = info.mVisible;
         mHasDirectActivity = info.mHasDirectActivity;
+        mDecorSurface = info.mDecorSurface;
     }
 
     /** The {@link Configuration} of the parent Task */
@@ -92,7 +99,13 @@
             return false;
         }
         return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId
-                && mVisible == that.mVisible && mHasDirectActivity == that.mHasDirectActivity;
+                && mVisible == that.mVisible && mHasDirectActivity == that.mHasDirectActivity
+                && mDecorSurface == that.mDecorSurface;
+    }
+
+    @Nullable
+    public SurfaceControl getDecorSurface() {
+        return mDecorSurface;
     }
 
     @WindowConfiguration.WindowingMode
@@ -107,6 +120,7 @@
                 + ", displayId=" + mDisplayId
                 + ", visible=" + mVisible
                 + ", hasDirectActivity=" + mHasDirectActivity
+                + ", decorSurface=" + mDecorSurface
                 + "}";
     }
 
@@ -128,7 +142,8 @@
         return mConfiguration.equals(that.mConfiguration)
                 && mDisplayId == that.mDisplayId
                 && mVisible == that.mVisible
-                && mHasDirectActivity == that.mHasDirectActivity;
+                && mHasDirectActivity == that.mHasDirectActivity
+                && mDecorSurface == that.mDecorSurface;
     }
 
     @Override
@@ -137,6 +152,7 @@
         result = 31 * result + mDisplayId;
         result = 31 * result + (mVisible ? 1 : 0);
         result = 31 * result + (mHasDirectActivity ? 1 : 0);
+        result = 31 * result + Objects.hashCode(mDecorSurface);
         return result;
     }
 
@@ -146,6 +162,7 @@
         dest.writeInt(mDisplayId);
         dest.writeBoolean(mVisible);
         dest.writeBoolean(mHasDirectActivity);
+        dest.writeTypedObject(mDecorSurface, flags);
     }
 
     private TaskFragmentParentInfo(Parcel in) {
@@ -153,6 +170,7 @@
         mDisplayId = in.readInt();
         mVisible = in.readBoolean();
         mHasDirectActivity = in.readBoolean();
+        mDecorSurface = in.readTypedObject(SurfaceControl.CREATOR);
     }
 
     public static final Creator<TaskFragmentParentInfo> CREATOR =
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 7c4252e..6b074a6 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -33,6 +33,7 @@
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TWO_FINGER_TRIPLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
 import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_ALL;
@@ -131,6 +132,18 @@
     }
 
     /**
+     * Logs magnification that is assigned to the two finger triple tap shortcut. Calls this when
+     * triggering the magnification two finger triple tap shortcut.
+     */
+    public static void logMagnificationTwoFingerTripleTap(boolean enabled) {
+        FrameworkStatsLog.write(FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED,
+                MAGNIFICATION_COMPONENT_NAME.flattenToString(),
+                // jean update
+                ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TWO_FINGER_TRIPLE_TAP,
+                convertToLoggingServiceStatus(enabled));
+    }
+
+    /**
      * Logs accessibility feature name that is assigned to the long pressed accessibility button
      * shortcut. Calls this when clicking the long pressed accessibility button shortcut.
      *
diff --git a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
index f5fe12e..e55c641 100644
--- a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
+++ b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
@@ -37,22 +37,11 @@
      * @return The highest refresh rate
      */
     public static float findHighestRefreshRateForDefaultDisplay(Context context) {
-        return findHighestRefreshRate(context, Display.DEFAULT_DISPLAY);
-    }
-
-    /**
-     * Find the highest refresh rate among all the modes of the specified display.
-     *
-     * @param context The context
-     * @param displayId The display ID
-     * @return The highest refresh rate
-     */
-    public static float findHighestRefreshRate(Context context, int displayId) {
         final DisplayManager dm = context.getSystemService(DisplayManager.class);
-        final Display display = dm.getDisplay(displayId);
+        final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
 
         if (display == null) {
-            Log.w(TAG, "No valid display device with ID = " + displayId);
+            Log.w(TAG, "No valid default display device");
             return DEFAULT_REFRESH_RATE;
         }
 
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index dadeb2b..aab2242 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -60,7 +60,8 @@
     @UnsupportedAppUsage(maxTargetSdk = 28)
     void notifyCallForwardingChanged(boolean cfi);
     void notifyCallForwardingChangedForSubscriber(in int subId, boolean cfi);
-    void notifyDataActivityForSubscriber(int phoneId, int subId, int state);
+    void notifyDataActivityForSubscriber(int subId, int state);
+    void notifyDataActivityForSubscriberWithSlot(int phoneId, int subId, int state);
     void notifyDataConnectionForSubscriber(
             int phoneId, int subId, in PreciseDataConnectionState preciseState);
     // Uses CellIdentity which is Parcelable here; will convert to CellLocation in client.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 440a332..f365dbb 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -462,9 +462,4 @@
             ],
         },
     },
-
-    // Workaround Clang LTO crash.
-    lto: {
-        never: true,
-    },
 }
diff --git a/core/proto/android/os/batteryusagestats.proto b/core/proto/android/os/batteryusagestats.proto
index 2b74220..11b367b 100644
--- a/core/proto/android/os/batteryusagestats.proto
+++ b/core/proto/android/os/batteryusagestats.proto
@@ -92,8 +92,24 @@
     message UidBatteryConsumer {
         optional int32 uid = 1;
         optional BatteryConsumerData battery_consumer_data = 2;
-        optional int64 time_in_foreground_millis = 3;
-        optional int64 time_in_background_millis = 4;
+        // DEPRECATED Use time_in_state instead.
+        optional int64 time_in_foreground_millis = 3 [deprecated = true];
+        // DEPRECATED Use time_in_state instead.
+        optional int64 time_in_background_millis = 4 [deprecated = true];
+
+        message TimeInState {
+            enum ProcessState {
+                UNSPECIFIED = 0;
+                FOREGROUND = 1;
+                BACKGROUND = 2;
+                FOREGROUND_SERVICE = 3;
+            }
+
+            optional ProcessState process_state = 1;
+            optional int64 time_in_state_millis = 2;
+        }
+
+        repeated TimeInState time_in_state = 5;
     }
     repeated UidBatteryConsumer uid_battery_consumers = 5;
 
diff --git a/core/proto/android/server/usagestatsservice_v2.proto b/core/proto/android/server/usagestatsservice_v2.proto
index d5cf60d..c242c9c 100644
--- a/core/proto/android/server/usagestatsservice_v2.proto
+++ b/core/proto/android/server/usagestatsservice_v2.proto
@@ -110,6 +110,7 @@
   optional int32 task_root_package_token = 11;
   optional int32 task_root_class_token = 12;
   optional int32 locus_id_token = 13;
+  optional ObfuscatedUserInteractionExtrasProto interaction_extras = 14;
 }
 
 /**
@@ -129,6 +130,7 @@
   optional string task_root_package = 11;
   optional string task_root_class = 12;
   optional string locus_id = 13 [(.android.privacy).dest = DEST_EXPLICIT];
+  optional bytes extras = 14;
 }
 
 /**
@@ -145,3 +147,11 @@
   // Stores the mappings for every package
   repeated PackagesMap packages_map = 2;
 }
+
+/**
+ * Store the relevant information from extra details for user interaction event.
+ */
+message ObfuscatedUserInteractionExtrasProto {
+  optional int32 category_token = 1;
+  optional int32 action_token = 2;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4d208c6..0021640 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7817,6 +7817,13 @@
     <permission android:name="android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT"
                 android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an app to track all preparations for a complete factory reset.
+     <p>Protection level: signature|privileged
+     @FlaggedApi("android.permission.flags.factory_reset_prep_permission_apis")
+     @hide -->
+    <permission android:name="android.permission.PREPARE_FACTORY_RESET"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 98897d8..1d4e01a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1522,6 +1522,10 @@
     config_screenBrightnessSettingDefaultFloat instead -->
     <integer name="config_screenBrightnessSettingDefault">102</integer>
 
+    <!-- Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode.
+    The value must be in the range [0, 255]. -->
+    <integer name="config_screenBrightnessCapForWearBedtimeMode">20</integer>
+
     <!-- Minimum screen brightness setting allowed by power manager.
          -2 is invalid so setting will resort to int value specified above.
          Set this to 0.0 to allow screen to go to minimal brightness.
@@ -4989,6 +4993,11 @@
     <!-- Component name for the default module metadata provider on this device -->
     <string name="config_defaultModuleMetadataProvider" translatable="false">com.android.modulemetadata</string>
 
+    <!-- Packages that contain a security state.
+         {@link SecurityStateManager#getGlobalSecurityState} will read and report the state/version
+          of these packages. -->
+    <string-array name="config_securityStatePackages" translatable="false" />
+
     <!-- Package name for the default Health Connect app.
          OEMs can set this with their own health app package name to define a default app with high
          priority for the app to store the health data. If set the app always has priority of 1
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 017688a..4b0fa4b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1274,6 +1274,7 @@
   <java-symbol type="array" name="policy_exempt_apps" />
   <java-symbol type="array" name="vendor_policy_exempt_apps" />
   <java-symbol type="array" name="cloneable_apps" />
+  <java-symbol type="array" name="config_securityStatePackages" />
 
   <java-symbol type="drawable" name="default_wallpaper" />
   <java-symbol type="drawable" name="default_lock_wallpaper" />
@@ -2078,6 +2079,7 @@
   <java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
   <java-symbol type="integer" name="config_screenBrightnessSettingDefault" />
+  <java-symbol type="integer" name="config_screenBrightnessCapForWearBedtimeMode" />
   <java-symbol type="dimen" name="config_screenBrightnessSettingMinimumFloat" />
   <java-symbol type="dimen" name="config_screenBrightnessSettingMaximumFloat" />
   <java-symbol type="dimen" name="config_screenBrightnessSettingDefaultFloat" />
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
index aaaa3c7..ac1f7d0 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
+++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
@@ -48,6 +48,11 @@
     private static final int UID_1 = 2000;
     private static final int UID_2 = 3000;
     private static final int UID_3 = 4000;
+    private static final int[] UID_USAGE_TIME_PROCESS_STATES = {
+            BatteryConsumer.PROCESS_STATE_FOREGROUND,
+            BatteryConsumer.PROCESS_STATE_BACKGROUND,
+            BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE
+    };
 
     @Test
     public void testGetStatsProto() {
@@ -195,6 +200,20 @@
         assertEquals("For uid " + uid,
                 uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND),
                 uidConsumerProto.timeInBackgroundMillis);
+        for (int processState : UID_USAGE_TIME_PROCESS_STATES) {
+            final long timeInStateMillis = uidConsumer.getTimeInProcessStateMs(processState);
+            if (timeInStateMillis <= 0) {
+                continue;
+            }
+            assertEquals("For uid " + uid + ", process state " + processState,
+                    timeInStateMillis,
+                    Arrays.stream(uidConsumerProto.timeInState)
+                            .filter(timeInState -> timeInState.processState == processState)
+                            .findFirst()
+                            .orElseThrow()
+                            .timeInStateMillis);
+        }
+
         if (expectNullBatteryConsumerData) {
             assertNull("For uid " + uid, uidConsumerProto.batteryConsumerData);
         } else {
@@ -250,8 +269,8 @@
         final UidBatteryConsumer.Builder uidBuilder = builder
                 .getOrCreateUidBatteryConsumerBuilder(UID_0)
                 .setPackageWithHighestDrain("myPackage0")
-                .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, 1000)
-                .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, 2000)
+                .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1000)
+                .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 2000)
                 .setConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
                 .setConsumedPower(
@@ -285,7 +304,7 @@
 
         builder.getOrCreateUidBatteryConsumerBuilder(UID_1)
                 .setPackageWithHighestDrain("myPackage1")
-                .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 1234);
+                .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1234);
 
         builder.getOrCreateUidBatteryConsumerBuilder(UID_2)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
@@ -331,8 +350,10 @@
         // significantly larger than 50 Kb
         for (int i = 0; i < 3000; i++) {
             builder.getOrCreateUidBatteryConsumerBuilder(i)
-                    .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 1 * 60 * 1000)
-                    .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND, 2 * 60 * 1000)
+                    .setTimeInProcessStateMs(
+                            BatteryConsumer.PROCESS_STATE_FOREGROUND, 1 * 60 * 1000)
+                    .setTimeInProcessStateMs(
+                            BatteryConsumer.PROCESS_STATE_BACKGROUND, 2 * 60 * 1000)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 30)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 40);
         }
@@ -340,16 +361,16 @@
         // Add a UID with much larger battery footprint
         final int largeConsumerUid = 3001;
         builder.getOrCreateUidBatteryConsumerBuilder(largeConsumerUid)
-                .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 10 * 60 * 1000)
-                .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND, 20 * 60 * 1000)
+                .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 10 * 60 * 1000)
+                .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 20 * 60 * 1000)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400);
 
         // Add a UID with much larger usage duration
         final int highUsageUid = 3002;
         builder.getOrCreateUidBatteryConsumerBuilder(highUsageUid)
-                .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 60 * 60 * 1000)
-                .setTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND, 120 * 60 * 1000)
+                .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 60 * 60 * 1000)
+                .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 120 * 60 * 1000)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 3)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 4);
 
diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
index 5a202c5..9dce899 100644
--- a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
+++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
@@ -28,6 +28,7 @@
 import android.app.usage.UsageEvents.Event;
 import android.content.res.Configuration;
 import android.os.Parcel;
+import android.os.PersistableBundle;
 import android.test.suitebuilder.annotation.LargeTest;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -140,6 +141,12 @@
             case Event.LOCUS_ID_SET:
                 event.mLocusId = anyString();
                 break;
+            case Event.USER_INTERACTION:
+                PersistableBundle extras = new PersistableBundle();
+                extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, anyString());
+                extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, anyString());
+                event.mExtras = extras;
+                break;
         }
 
         event.mFlags = anyInt();
@@ -176,6 +183,14 @@
             case Event.LOCUS_ID_SET:
                 assertEquals(ue1.mLocusId, ue2.mLocusId);
                 break;
+            case Event.USER_INTERACTION:
+                final PersistableBundle extras1 = ue1.getExtras();
+                final PersistableBundle extras2 = ue2.getExtras();
+                assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY),
+                        extras2.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY));
+                assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_ACTION),
+                        extras2.getString(UsageStatsManager.EXTRA_EVENT_ACTION));
+                break;
         }
 
         assertEquals(ue1.mFlags, ue2.mFlags);
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
index 083e37a..fae7148 100644
--- a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
@@ -72,7 +72,7 @@
             "mShortcutId", "mShortcutIdToken", "mBucketAndReason", "mInstanceId",
             "mNotificationChannelId", "mNotificationChannelIdToken", "mTaskRootPackage",
             "mTaskRootPackageToken", "mTaskRootClass", "mTaskRootClassToken", "mLocusId",
-            "mLocusIdToken"};
+            "mLocusIdToken", "mExtras", "mUserInteractionExtrasToken"};
     // All fields in this list are defined in UsageEvents.Event but not persisted
     private static final String[] USAGEEVENTS_IGNORED_FIELDS = {"mAction", "mContentAnnotations",
             "mContentType", "DEVICE_EVENT_PACKAGE_NAME", "FLAG_IS_PACKAGE_INSTANT_APP",
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 87c167c..4a9cb71 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -83,6 +83,12 @@
             }
 
             @Override
+            protected AssetManager createAssetManager(@NonNull final ResourcesKey key,
+                    ResourcesManager.ApkAssetsSupplier apkSupplier) {
+                return createAssetManager(key);
+            }
+
+            @Override
             protected DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments daj) {
                 return mDisplayMetricsMap.get(displayId);
             }
@@ -100,7 +106,7 @@
                 null, APP_ONE_RES_DIR, null, null, null, null, null, null,
                 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
         assertNotNull(newResources);
-        assertSame(resources, newResources);
+        assertSame(resources.getImpl(), newResources.getImpl());
     }
 
     @SmallTest
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index d36ac39..2237ba1 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -529,6 +529,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ActivityStarter.java"
     },
+    "-1582845629": {
+      "message": "Starting animation on %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java"
+    },
     "-1575977269": {
       "message": "Skipping %s: mismatch root %s",
       "level": "DEBUG",
@@ -925,6 +931,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "-1243510456": {
+      "message": "Dim animation requested: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java"
+    },
     "-1237827119": {
       "message": "Schedule remove starting %s startingWindow=%s animate=%b Callers=%s",
       "level": "VERBOSE",
@@ -1171,6 +1183,12 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/BackNavigationController.java"
     },
+    "-1028213464": {
+      "message": "%s skipping animation and directly setting alpha=%f, blur=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_DIMMER",
+      "at": "com\/android\/server\/wm\/DimmerAnimationHelper.java"
+    },
     "-1022146708": {
       "message": "Skipping %s: mismatch activity type",
       "level": "DEBUG",
@@ -1795,12 +1813,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-504637678": {
-      "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_DIMMER",
-      "at": "com\/android\/server\/wm\/SmoothDimmer.java"
-    },
     "-503656156": {
       "message": "Update process config of %s to new config %s",
       "level": "VERBOSE",
@@ -4027,12 +4039,6 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "1620751818": {
-      "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_DIMMER",
-      "at": "com\/android\/server\/wm\/SmoothDimmer.java"
-    },
     "1621562070": {
       "message": "    startWCT=%s",
       "level": "VERBOSE",
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index 5e16bce..dd703f5 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -33,7 +33,6 @@
 import android.util.Log;
 
 import java.util.Calendar;
-import java.util.Objects;
 
 /**
  * @hide This should not be made public in its present form because it
@@ -139,13 +138,25 @@
         return new KeyStore2();
     }
 
+    /**
+     * Gets the {@link IKeystoreService} that should be started in early_hal in Android.
+     *
+     * @throws IllegalStateException if the KeystoreService is not available or has not
+     * been initialized when called. This is a state that should not happen and indicates
+     * and error somewhere in the stack or with the calling processes access permissions.
+     */
     @NonNull private synchronized IKeystoreService getService(boolean retryLookup) {
         if (mBinder == null || retryLookup) {
             mBinder = IKeystoreService.Stub.asInterface(ServiceManager
-                    .getService(KEYSTORE2_SERVICE_NAME));
-            Binder.allowBlocking(mBinder.asBinder());
+                .getService(KEYSTORE2_SERVICE_NAME));
         }
-        return Objects.requireNonNull(mBinder);
+        if (mBinder == null) {
+            throw new IllegalStateException(
+                    "Could not connect to Keystore service. Keystore may have crashed or not been"
+                            + " initialized");
+        }
+        Binder.allowBlocking(mBinder.asBinder());
+        return mBinder;
     }
 
     void delete(KeyDescriptor descriptor) throws KeyStoreException {
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 50cfd94..4c2433f 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
@@ -443,7 +443,8 @@
         assertThat(taskContainer.getTaskFragmentContainers()).containsExactly(overlayContainer);
 
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(Configuration.EMPTY,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */));
 
         mSplitController.updateOverlayContainer(mTransaction, 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 02031a6..8c274a2 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
@@ -1139,7 +1139,8 @@
     public void testOnTransactionReady_taskFragmentParentInfoChanged() {
         final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
         final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */);
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */);
         transaction.addChange(new TaskFragmentTransaction.Change(
                 TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
                 .setTaskId(TASK_ID)
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index e56c8ab..7b77235 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -79,14 +79,16 @@
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */));
 
         assertEquals(WINDOWING_MODE_MULTI_WINDOW,
                 taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */));
 
         assertEquals(WINDOWING_MODE_FREEFORM,
                 taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
@@ -106,13 +108,15 @@
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */));
 
         assertFalse(taskContainer.isInPictureInPicture());
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */));
+                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */));
 
         assertTrue(taskContainer.isInPictureInPicture());
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 8f904c4..45c948b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -537,8 +537,8 @@
                 return;
             }
 
-            final boolean clickedBubbleIsCurrentlyExpandedBubble =
-                    clickedBubble.getKey().equals(mExpandedBubble.getKey());
+            final boolean clickedBubbleIsCurrentlyExpandedBubble = mExpandedBubble != null
+                            && clickedBubble.getKey().equals(mExpandedBubble.getKey());
 
             if (isExpanded()) {
                 mExpandedAnimationController.onGestureFinished();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 271a3b2..63afd3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -627,7 +627,8 @@
                         && mRecentsTask.equals(change.getContainer());
                 hasTaskChange = hasTaskChange || isRootTask;
                 final boolean isLeafTask = leafTaskFilter.test(change);
-                if (TransitionUtil.isOpeningType(change.getMode())) {
+                if (TransitionUtil.isOpeningType(change.getMode())
+                        || TransitionUtil.isOrderOnly(change)) {
                     if (isRecentsTask) {
                         recentsOpening = change;
                     } else if (isRootTask || isLeafTask) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index ef8393c..35a1fa0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -151,7 +151,14 @@
 
     @Override
     public void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
-        runOnViewThread(() -> setResizeBackgroundColor(t, bgColor));
+        if (mHandler.getLooper().isCurrentThread()) {
+            // We can only use the transaction if it can updated synchronously, otherwise the tx
+            // will be applied immediately after but also used/updated on the view thread which
+            // will lead to a race and/or crash
+            runOnViewThread(() -> setResizeBackgroundColor(t, bgColor));
+        } else {
+            runOnViewThread(() -> setResizeBackgroundColor(bgColor));
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8cbcde3..53ec201 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -49,7 +49,6 @@
 import android.view.ViewConfiguration;
 import android.view.WindowManagerGlobal;
 
-import com.android.internal.view.BaseIWindow;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 
@@ -70,7 +69,9 @@
     private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
 
     private final int mDisplayId;
-    private final BaseIWindow mFakeWindow;
+
+    private final IBinder mClientToken;
+
     private final IBinder mFocusGrantToken;
     private final SurfaceControl mDecorationSurface;
     private final InputChannel mInputChannel;
@@ -78,7 +79,7 @@
     private final DragPositioningCallback mCallback;
 
     private final SurfaceControl mInputSinkSurface;
-    private final BaseIWindow mFakeSinkWindow;
+    private final IBinder mSinkClientToken;
     private final InputChannel mSinkInputChannel;
     private final DisplayController mDisplayController;
 
@@ -116,17 +117,14 @@
         mTaskCornerRadius = taskCornerRadius;
         mDecorationSurface = decorationSurface;
         mDisplayController = displayController;
-        // Use a fake window as the backing surface is a container layer, and we don't want to
-        // create a buffer layer for it, so we can't use ViewRootImpl.
-        mFakeWindow = new BaseIWindow();
-        mFakeWindow.setSession(mWindowSession);
+        mClientToken = new Binder();
         mFocusGrantToken = new Binder();
         mInputChannel = new InputChannel();
         try {
             mWindowSession.grantInputChannel(
                     mDisplayId,
                     mDecorationSurface,
-                    mFakeWindow.asBinder(),
+                    mClientToken,
                     null /* hostInputToken */,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
@@ -155,13 +153,13 @@
                 .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
                 .show(mInputSinkSurface)
                 .apply();
-        mFakeSinkWindow = new BaseIWindow();
+        mSinkClientToken = new Binder();
         mSinkInputChannel = new InputChannel();
         try {
             mWindowSession.grantInputChannel(
                     mDisplayId,
                     mInputSinkSurface,
-                    mFakeSinkWindow.asBinder(),
+                    mSinkClientToken,
                     null /* hostInputToken */,
                     FLAG_NOT_FOCUSABLE,
                     0 /* privateFlags */,
@@ -324,14 +322,14 @@
         mInputEventReceiver.dispose();
         mInputChannel.dispose();
         try {
-            mWindowSession.remove(mFakeWindow.asBinder());
+            mWindowSession.remove(mClientToken);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
 
         mSinkInputChannel.dispose();
         try {
-            mWindowSession.remove(mFakeSinkWindow.asBinder());
+            mWindowSession.remove(mSinkClientToken);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index 6429b00..fafd37b 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -45,12 +45,15 @@
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
+        <option name="run-command" value="settings put global package_verifier_user_consent -1"/>
         <option name="teardown-command"
                 value="settings delete secure show_ime_with_hard_keyboard"/>
         <option name="teardown-command" value="settings delete system show_touches"/>
         <option name="teardown-command" value="settings delete system pointer_location"/>
         <option name="teardown-command"
                 value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+        <option name="teardown-command"
+                value="settings put global package_verifier_user_consent 1"/>
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
@@ -76,6 +79,8 @@
                 value="appops set com.android.shell android:mock_location deny"/>
     </target_preparer>
 
+    <target_preparer class="com.android.csuite.core.AppCrawlTesterHostPreparer"/>
+
     <!-- Use app crawler to log into Netflix -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java
new file mode 100644
index 0000000..b91d6f9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestHandler.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+
+/**
+ * Basic test handler that immediately executes anything that is posted on it.
+ */
+public class TestHandler extends Handler {
+    public TestHandler(Looper looper) {
+        super(looper);
+    }
+
+    @Override
+    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+        dispatchMessage(msg);
+        return true;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 4afb29e..d7c4610 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -40,6 +40,7 @@
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -58,6 +59,7 @@
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestHandler;
 import com.android.wm.shell.common.HandlerExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable;
@@ -92,8 +94,7 @@
     Transitions mTransitions;
     @Mock
     Looper mViewLooper;
-    @Mock
-    Handler mViewHandler;
+    TestHandler mViewHandler;
 
     SurfaceSession mSession;
     SurfaceControl mLeash;
@@ -112,7 +113,7 @@
 
         mContext = getContext();
         doReturn(true).when(mViewLooper).isCurrentThread();
-        doReturn(mViewLooper).when(mViewHandler).getLooper();
+        mViewHandler = spy(new TestHandler(mViewLooper));
 
         mTaskInfo = new ActivityManager.RunningTaskInfo();
         mTaskInfo.token = mToken;
@@ -668,4 +669,24 @@
         mTaskViewTaskController.onTaskInfoChanged(mTaskInfo);
         verify(mViewHandler).post(any());
     }
+
+    @Test
+    public void testSetResizeBgOnSameUiThread_expectUsesTransaction() {
+        SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
+        mTaskView = spy(mTaskView);
+        mTaskView.setResizeBgColor(tx, Color.BLUE);
+        verify(mViewHandler, never()).post(any());
+        verify(mTaskView, never()).setResizeBackgroundColor(eq(Color.BLUE));
+        verify(mTaskView).setResizeBackgroundColor(eq(tx), eq(Color.BLUE));
+    }
+
+    @Test
+    public void testSetResizeBgOnDifferentUiThread_expectDoesNotUseTransaction() {
+        doReturn(false).when(mViewLooper).isCurrentThread();
+        SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
+        mTaskView = spy(mTaskView);
+        mTaskView.setResizeBgColor(tx, Color.BLUE);
+        verify(mViewHandler).post(any());
+        verify(mTaskView).setResizeBackgroundColor(eq(Color.BLUE));
+    }
 }
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index a1d6ab5..b1cf96d 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -113,13 +113,13 @@
   }
 
   public final class GnssMeasurementRequest implements android.os.Parcelable {
-    method @NonNull public android.os.WorkSource getWorkSource();
+    method @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE) @NonNull public android.os.WorkSource getWorkSource();
     method public boolean isCorrelationVectorOutputsEnabled();
   }
 
   public static final class GnssMeasurementRequest.Builder {
     method @NonNull public android.location.GnssMeasurementRequest.Builder setCorrelationVectorOutputsEnabled(boolean);
-    method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.GnssMeasurementRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
+    method @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE) @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.GnssMeasurementRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
   }
 
   public final class GnssReflectingPlane implements android.os.Parcelable {
diff --git a/location/java/android/location/GnssMeasurementRequest.java b/location/java/android/location/GnssMeasurementRequest.java
index 65af392..2f0835a 100644
--- a/location/java/android/location/GnssMeasurementRequest.java
+++ b/location/java/android/location/GnssMeasurementRequest.java
@@ -17,11 +17,13 @@
 package android.location;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.location.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.WorkSource;
@@ -121,6 +123,7 @@
      *
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE)
     @SystemApi
     public @NonNull WorkSource getWorkSource() {
         return mWorkSource;
@@ -298,6 +301,7 @@
          *
          * @hide
          */
+        @FlaggedApi(Flags.FLAG_GNSS_API_MEASUREMENT_REQUEST_WORK_SOURCE)
         @SystemApi
         @RequiresPermission(Manifest.permission.UPDATE_DEVICE_STATS)
         public @NonNull Builder setWorkSource(@Nullable WorkSource workSource) {
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index c471a27..b6055e8 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -5,4 +5,18 @@
     namespace: "location"
     description: "Flag for GNSS API for NavIC L1"
     bug: "302199306"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "gnss_call_stop_before_set_position_mode"
+    namespace: "location"
+    description: "Flag for calling stop() before setPositionMode()"
+    bug: "306874828"
+}
+
+flag {
+    name: "gnss_api_measurement_request_work_source"
+    namespace: "location"
+    description: "Flag for GnssMeasurementRequest WorkSource API"
+    bug: "295235160"
+}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index d14775f..5c8758a 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -739,7 +739,7 @@
 
     oneway void stopLoudnessCodecUpdates(int piid);
 
-    oneway void addLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo);
+    oneway void addLoudnessCodecInfo(int piid, int mediaCodecHash, in LoudnessCodecInfo codecInfo);
 
     oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo);
 
diff --git a/media/java/android/media/LoudnessCodecConfigurator.java b/media/java/android/media/LoudnessCodecConfigurator.java
index 92f3372..de9d87c0 100644
--- a/media/java/android/media/LoudnessCodecConfigurator.java
+++ b/media/java/android/media/LoudnessCodecConfigurator.java
@@ -30,10 +30,10 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -47,9 +47,6 @@
  * parameter updates are defined by the CTA-2075 standard.
  * <p>A new object should be instantiated for each {@link AudioTrack} with the help
  * of {@link #create()} or {@link #create(Executor, OnLoudnessCodecUpdateListener)}.
- *
- * TODO: remove hide once API is final
- * @hide
  */
 @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
 public class LoudnessCodecConfigurator {
@@ -57,9 +54,6 @@
 
     /**
      * Listener used for receiving asynchronous loudness metadata updates.
-     *
-     * TODO: remove hide once API is final
-     * @hide
      */
     @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
     public interface OnLoudnessCodecUpdateListener {
@@ -76,9 +70,6 @@
          * @return a Bundle which contains the original computed codecValues
          * aggregated with user edits. The platform will configure the associated
          * MediaCodecs with the returned Bundle params.
-         *
-         * TODO: remove hide once API is final
-         * @hide
          */
         @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
         @NonNull
@@ -112,9 +103,6 @@
      * Otherwise, use {@link #create(Executor, OnLoudnessCodecUpdateListener)}.
      *
      * @return the {@link LoudnessCodecConfigurator} instance
-     *
-     * TODO: remove hide once API is final
-     * @hide
      */
     @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
     public static @NonNull LoudnessCodecConfigurator create() {
@@ -133,9 +121,6 @@
      * @param listener used for receiving updates
      *
      * @return the {@link LoudnessCodecConfigurator} instance
-     *
-     * TODO: remove hide once API is final
-     * @hide
      */
     @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
     public static @NonNull LoudnessCodecConfigurator create(
@@ -200,12 +185,9 @@
      *                   method will have the effect of clearing the existing set
      *                   {@link AudioTrack} and will stop receiving asynchronous
      *                   loudness updates
-     *
-     * TODO: remove hide once API is final
-     * @hide
      */
     @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void setAudioTrack(AudioTrack audioTrack) {
+    public void setAudioTrack(@Nullable AudioTrack audioTrack) {
         List<LoudnessCodecInfo> codecInfos;
         int piid = PLAYER_PIID_INVALID;
         int oldPiid = PLAYER_PIID_INVALID;
@@ -250,10 +232,11 @@
      * previously added.
      *
      * @param mediaCodec the codec to start receiving asynchronous loudness
-     *                   updates
-     *
-     * TODO: remove hide once API is final
-     * @hide
+     *                   updates. The codec has to be in a configured or started
+     *                   state in order to add it for loudness updates.
+     * @throws IllegalArgumentException if the {@code mediaCodec} was not configured,
+     *                                  does not contain loudness metadata or if it
+     *                                  was already added before
      */
     @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
     public void addMediaCodec(@NonNull MediaCodec mediaCodec) {
@@ -262,30 +245,31 @@
         int piid = PLAYER_PIID_INVALID;
         final LoudnessCodecInfo mcInfo = getCodecInfo(mc);
 
-        if (mcInfo != null) {
-            synchronized (mConfiguratorLock) {
-                final AtomicBoolean containsCodec = new AtomicBoolean(false);
-                Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> {
-                    containsCodec.set(!codecSet.add(mc));
-                    return codecSet;
-                });
-                if (newSet == null) {
-                    newSet = new HashSet<>();
-                    newSet.add(mc);
-                    mMediaCodecs.put(mcInfo, newSet);
-                }
-                if (containsCodec.get()) {
-                    Log.v(TAG, "Loudness configurator already added media codec " + mediaCodec);
-                    return;
-                }
-                if (mAudioTrack != null) {
-                    piid = mAudioTrack.getPlayerIId();
-                }
+        if (mcInfo == null) {
+            throw new IllegalArgumentException("Could not extract codec loudness information");
+        }
+        synchronized (mConfiguratorLock) {
+            final AtomicBoolean containsCodec = new AtomicBoolean(false);
+            Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> {
+                containsCodec.set(!codecSet.add(mc));
+                return codecSet;
+            });
+            if (newSet == null) {
+                newSet = new HashSet<>();
+                newSet.add(mc);
+                mMediaCodecs.put(mcInfo, newSet);
             }
+            if (containsCodec.get()) {
+                throw new IllegalArgumentException(
+                        "Loudness configurator already added " + mediaCodec);
+            }
+            if (mAudioTrack != null) {
+                piid = mAudioTrack.getPlayerIId();
+            }
+        }
 
-            if (piid != PLAYER_PIID_INVALID) {
-                mLcDispatcher.addLoudnessCodecInfo(piid, mcInfo);
-            }
+        if (piid != PLAYER_PIID_INVALID) {
+            mLcDispatcher.addLoudnessCodecInfo(piid, mediaCodec.hashCode(), mcInfo);
         }
     }
 
@@ -297,37 +281,44 @@
      * <p>No elements will be removed if the passed mediaCodec was not added before.
      *
      * @param mediaCodec the element to remove for receiving asynchronous updates
-     *
-     * TODO: remove hide once API is final
-     * @hide
+     * @throws IllegalArgumentException if the {@code mediaCodec} was not configured,
+     *                                  does not contain loudness metadata or if it
+     *                                  was not added before
      */
     @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
     public void removeMediaCodec(@NonNull MediaCodec mediaCodec) {
         int piid = PLAYER_PIID_INVALID;
         LoudnessCodecInfo mcInfo;
-        AtomicBoolean removed = new AtomicBoolean(false);
+        AtomicBoolean removedMc = new AtomicBoolean(false);
+        AtomicBoolean removeInfo = new AtomicBoolean(false);
 
         mcInfo = getCodecInfo(Objects.requireNonNull(mediaCodec,
                 "MediaCodec for removeMediaCodec cannot be null"));
 
-        if (mcInfo != null) {
-            synchronized (mConfiguratorLock) {
-                if (mAudioTrack != null) {
-                    piid = mAudioTrack.getPlayerIId();
+        if (mcInfo == null) {
+            throw new IllegalArgumentException("Could not extract codec loudness information");
+        }
+        synchronized (mConfiguratorLock) {
+            if (mAudioTrack != null) {
+                piid = mAudioTrack.getPlayerIId();
+            }
+            mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> {
+                removedMc.set(mcs.remove(mediaCodec));
+                if (mcs.isEmpty()) {
+                    // remove the entry
+                    removeInfo.set(true);
+                    return null;
                 }
-                mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> {
-                    removed.set(mcs.remove(mediaCodec));
-                    if (mcs.isEmpty()) {
-                        // remove the entry
-                        return null;
-                    }
-                    return mcs;
-                });
+                return mcs;
+            });
+            if (!removedMc.get()) {
+                throw new IllegalArgumentException(
+                        "Loudness configurator does not contain " + mediaCodec);
             }
+        }
 
-            if (piid != PLAYER_PIID_INVALID && removed.get()) {
-                mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo);
-            }
+        if (piid != PLAYER_PIID_INVALID && removeInfo.get()) {
+            mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo);
         }
     }
 
@@ -342,9 +333,6 @@
      *
      * @return the {@link Bundle} containing the current loudness parameters. Caller is
      * responsible to update the {@link MediaCodec}
-     *
-     * TODO: remove hide once API is final
-     * @hide
      */
     @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
     @NonNull
@@ -375,9 +363,9 @@
     }
 
     /** @hide */
-    /*package*/ List<MediaCodec> getRegisteredMediaCodecList() {
+    /*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() {
         synchronized (mConfiguratorLock) {
-            return mMediaCodecs.values().stream().flatMap(Collection::stream).toList();
+            return mMediaCodecs;
         }
     }
 
@@ -397,40 +385,43 @@
             return null;
         }
 
-        final MediaFormat inputFormat = mediaCodec.getInputFormat();
-        final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME);
-        if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) {
-            // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize one of
-            // these two keys
-            int aacProfile = -1;
-            int profile = -1;
-            try {
-                aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE);
-            } catch (NullPointerException e) {
-                // does not contain KEY_AAC_PROFILE. do nothing
-            }
-            try {
-                profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE);
-            } catch (NullPointerException e) {
-                // does not contain KEY_PROFILE. do nothing
-            }
-            if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE
-                    || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) {
-                lci.metadataType = CODEC_METADATA_TYPE_MPEG_D;
+        try {
+            final MediaFormat inputFormat = mediaCodec.getInputFormat();
+            final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME);
+            if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) {
+                // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize
+                // one of these two keys
+                int aacProfile = -1;
+                int profile = -1;
+                try {
+                    aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE);
+                } catch (NullPointerException e) {
+                    // does not contain KEY_AAC_PROFILE. do nothing
+                }
+                try {
+                    profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE);
+                } catch (NullPointerException e) {
+                    // does not contain KEY_PROFILE. do nothing
+                }
+                if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE
+                        || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) {
+                    lci.metadataType = CODEC_METADATA_TYPE_MPEG_D;
+                } else {
+                    lci.metadataType = CODEC_METADATA_TYPE_MPEG_4;
+                }
             } else {
-                lci.metadataType = CODEC_METADATA_TYPE_MPEG_4;
+                Log.w(TAG, "MediaCodec mime type not supported for loudness annotation");
+                return null;
             }
-        } else {
-            Log.w(TAG, "MediaCodec mime type not supported for loudness annotation");
+
+            final MediaFormat outputFormat = mediaCodec.getOutputFormat();
+            lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
+                    < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "MediaCodec is not configured", e);
             return null;
         }
 
-        final MediaFormat outputFormat = mediaCodec.getOutputFormat();
-        lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
-                < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
-
-        lci.mediaCodecHashCode = mediaCodec.hashCode();
-
         return lci;
     }
 }
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
index be881b1..b546a81 100644
--- a/media/java/android/media/LoudnessCodecDispatcher.java
+++ b/media/java/android/media/LoudnessCodecDispatcher.java
@@ -27,12 +27,16 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -52,6 +56,9 @@
         private final CallbackUtil.LazyListenerManager<OnLoudnessCodecUpdateListener>
                 mLoudnessListenerMgr = new CallbackUtil.LazyListenerManager<>();
 
+        private final Object mLock = new Object();
+
+        @GuardedBy("mLock")
         private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator>
                 mConfiguratorListener = new HashMap<>();
 
@@ -66,38 +73,56 @@
 
         @Override
         public void dispatchLoudnessCodecParameterChange(int piid, PersistableBundle params) {
-            mLoudnessListenerMgr.callListeners(listener ->
+            if (DEBUG) {
+                Log.d(TAG, "dispatchLoudnessCodecParameterChange for piid " + piid
+                        + " persistable bundle: " + params);
+            }
+            mLoudnessListenerMgr.callListeners(listener -> {
+                synchronized (mLock) {
                     mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> {
                         // send the appropriate bundle for the user to update
                         if (lcConfig.getAssignedTrackPiid() == piid) {
-                            final List<MediaCodec> mediaCodecs =
-                                    lcConfig.getRegisteredMediaCodecList();
-                            for (MediaCodec mediaCodec : mediaCodecs) {
-                                final String infoKey = Integer.toString(mediaCodec.hashCode());
+                            final Map<LoudnessCodecInfo, Set<MediaCodec>> mediaCodecsMap =
+                                    lcConfig.getRegisteredMediaCodecs();
+                            for (LoudnessCodecInfo codecInfo : mediaCodecsMap.keySet()) {
+                                final String infoKey = Integer.toString(codecInfo.hashCode());
+                                Bundle bundle = null;
                                 if (params.containsKey(infoKey)) {
-                                    Bundle bundle = new Bundle(
-                                            params.getPersistableBundle(infoKey));
-                                    if (DEBUG) {
-                                        Log.d(TAG,
-                                                "Received for piid " + piid + " bundle: " + bundle);
+                                    bundle = new Bundle(params.getPersistableBundle(infoKey));
+                                }
+
+                                final Set<MediaCodec> mediaCodecs = mediaCodecsMap.get(codecInfo);
+                                for (MediaCodec mediaCodec : mediaCodecs) {
+                                    final String mediaCodecKey = Integer.toString(
+                                            mediaCodec.hashCode());
+                                    if (bundle == null && !params.containsKey(mediaCodecKey)) {
+                                        continue;
+                                    }
+                                    boolean canBreak = false;
+                                    if (bundle == null) {
+                                        // key was set by media codec hash to update single codec
+                                        bundle = new Bundle(
+                                                params.getPersistableBundle(mediaCodecKey));
+                                        canBreak = true;
                                     }
                                     bundle =
                                             LoudnessCodecUpdatesDispatcherStub.filterLoudnessParams(
-                                                    l.onLoudnessCodecUpdate(mediaCodec, bundle));
-                                    if (DEBUG) {
-                                        Log.d(TAG, "User changed for piid " + piid
-                                                + " to filtered bundle: " + bundle);
-                                    }
+                                                    l.onLoudnessCodecUpdate(mediaCodec,
+                                                            bundle));
 
                                     if (!bundle.isDefinitelyEmpty()) {
                                         mediaCodec.setParameters(bundle);
                                     }
+                                    if (canBreak) {
+                                        break;
+                                    }
                                 }
                             }
                         }
-
                         return lcConfig;
-                    }));
+                    });
+                }
+            });
         }
 
         private static Bundle filterLoudnessParams(Bundle bundle) {
@@ -130,21 +155,33 @@
             mLoudnessListenerMgr.addListener(
                     executor, listener, "addLoudnessCodecListener",
                     () -> dispatcher);
-            mConfiguratorListener.put(listener, configurator);
+            synchronized (mLock) {
+                mConfiguratorListener.put(listener, configurator);
+            }
         }
 
         void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) {
             Objects.requireNonNull(configurator);
 
-            for (Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e :
-                    mConfiguratorListener.entrySet()) {
-                if (e.getValue() == configurator) {
-                    final OnLoudnessCodecUpdateListener listener = e.getKey();
-                    mConfiguratorListener.remove(listener);
-                    mLoudnessListenerMgr.removeListener(listener, "removeLoudnessCodecListener");
-                    break;
+            OnLoudnessCodecUpdateListener listenerToRemove = null;
+            synchronized (mLock) {
+                Iterator<Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator>> iterator =
+                        mConfiguratorListener.entrySet().iterator();
+                while (iterator.hasNext()) {
+                    Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e =
+                            iterator.next();
+                    if (e.getValue() == configurator) {
+                        final OnLoudnessCodecUpdateListener listener = e.getKey();
+                        iterator.remove();
+                        listenerToRemove = listener;
+                        break;
+                    }
                 }
             }
+            if (listenerToRemove != null) {
+                mLoudnessListenerMgr.removeListener(listenerToRemove,
+                        "removeLoudnessCodecListener");
+            }
         }
     }
 
@@ -202,9 +239,10 @@
     }
 
     /** @hide */
-    public void addLoudnessCodecInfo(int piid, @NonNull LoudnessCodecInfo mcInfo) {
+    public void addLoudnessCodecInfo(int piid, int mediaCodecHash,
+            @NonNull LoudnessCodecInfo mcInfo) {
         try {
-            mAudioService.addLoudnessCodecInfo(piid, mcInfo);
+            mAudioService.addLoudnessCodecInfo(piid, mediaCodecHash, mcInfo);
         }  catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/LoudnessCodecInfo.aidl b/media/java/android/media/LoudnessCodecInfo.aidl
index fd69517..0ac5646 100644
--- a/media/java/android/media/LoudnessCodecInfo.aidl
+++ b/media/java/android/media/LoudnessCodecInfo.aidl
@@ -23,7 +23,7 @@
  *
  * {@hide}
  */
-@JavaDerive(equals = true)
+@JavaDerive(equals = true, toString = true)
 parcelable LoudnessCodecInfo {
     /** Supported codec metadata types for loudness updates. */
     @Backing(type="int")
@@ -37,7 +37,6 @@
         CODEC_METADATA_TYPE_DTS_UHD = 6
     }
 
-    int mediaCodecHashCode;
     CodecMetadataType metadataType;
     boolean isDownmixing;
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index aa5a290..2b52c4b 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -16,6 +16,10 @@
 
 package android.media.tv.ad;
 
+import android.annotation.FlaggedApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.media.tv.flags.Flags;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -23,14 +27,17 @@
 /**
  * Central system API to the overall client-side TV AD architecture, which arbitrates interaction
  * between applications and AD services.
- * @hide
  */
+@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
+@SystemService(Context.TV_AD_SERVICE)
 public class TvAdManager {
+    // TODO: implement more methods and unhide APIs.
     private static final String TAG = "TvAdManager";
 
     private final ITvAdManager mService;
     private final int mUserId;
 
+    /** @hide */
     public TvAdManager(ITvAdManager service, int userId) {
         mService = service;
         mUserId = userId;
@@ -38,6 +45,7 @@
 
     /**
      * The Session provides the per-session functionality of AD service.
+     * @hide
      */
     public static final class Session {
         private final IBinder mToken;
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index a73d1ff..018eaf6 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -5,4 +5,11 @@
     namespace: "media_tv"
     description: "Constants for standardizing broadcast visibility types."
     bug: "222402395"
+}
+
+flag {
+    name: "enable_ad_service_fw"
+    namespace: "media_tv"
+    description: "Enable the TV client-side AD framework."
+    bug: "303506816"
 }
\ No newline at end of file
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
index 65a9799..c9e36b7 100644
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
@@ -208,7 +208,7 @@
         verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
 
         mLcc.addMediaCodec(createAndConfigureMediaCodec());
-        verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+        verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any());
     }
 
     @Test
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index fe26dc3..991fe41 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -16,10 +16,12 @@
 
     dex_preopt: {
         profile_guided: true,
+        //TODO: b/312357299 - Update baseline profile
         profile: "profile.txt.prof",
     },
 
     static_libs: [
+        "CredentialManagerShared",
         "PlatformComposeCore",
         "androidx.activity_activity-compose",
         "androidx.appcompat_appcompat",
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
index 42f1207..325d3f8 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
@@ -16,6 +16,7 @@
 
 package com.android.credentialmanager
 
+import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.credentials.ui.RequestInfo
@@ -27,10 +28,10 @@
 import com.android.credentialmanager.model.Request
 
 fun Intent.parse(
-    packageManager: PackageManager,
+    context: Context,
 ): Request {
-    return parseCancelUiRequest(packageManager)
-        ?: parseRequestInfo()
+    return parseCancelUiRequest(context.packageManager)
+        ?: parseRequestInfo(context)
 }
 
 fun Intent.parseCancelUiRequest(packageManager: PackageManager): Request? =
@@ -51,11 +52,11 @@
         }
     }
 
-fun Intent.parseRequestInfo(): Request =
+fun Intent.parseRequestInfo(context: Context): Request =
     requestInfo.let{ info ->
         when (info?.type) {
             RequestInfo.TYPE_CREATE -> Request.Create(info.token)
-            RequestInfo.TYPE_GET -> toGet()
+            RequestInfo.TYPE_GET -> toGet(context)
             else -> {
                 throw IllegalStateException("Unrecognized request type: ${info?.type}")
             }
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
index 83183b5..3ef65b0 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
@@ -16,8 +16,8 @@
 
 package com.android.credentialmanager.client.impl
 
+import android.content.Context
 import android.content.Intent
-import android.content.pm.PackageManager
 import android.credentials.ui.BaseDialogResult
 import android.credentials.ui.UserSelectionDialogResult
 import android.os.Bundle
@@ -26,12 +26,13 @@
 import com.android.credentialmanager.model.Request
 import com.android.credentialmanager.parse
 import com.android.credentialmanager.client.CredentialManagerClient
+import dagger.hilt.android.qualifiers.ApplicationContext
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import javax.inject.Inject
 
 class CredentialManagerClientImpl @Inject constructor(
-        private val packageManager: PackageManager,
+    @ApplicationContext private val context: Context,
 ) : CredentialManagerClient {
 
     private val _requests = MutableStateFlow<Request?>(null)
@@ -40,7 +41,7 @@
 
     override fun updateRequest(intent: Intent) {
         val request = intent.parse(
-            packageManager = packageManager,
+            context = context,
         )
         Log.d(TAG, "Request parsed: $request, client instance: $this")
         if (request is Request.Cancel || request is Request.Close) {
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
new file mode 100644
index 0000000..f063074
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -0,0 +1,369 @@
+/*
+ * 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.credentialmanager.ktx
+
+import android.app.slice.Slice
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.credentials.Credential
+import android.credentials.flags.Flags
+import android.credentials.ui.AuthenticationEntry
+import android.credentials.ui.Entry
+import android.credentials.ui.GetCredentialProviderData
+import android.graphics.drawable.Drawable
+import android.text.TextUtils
+import android.util.Log
+import androidx.activity.result.IntentSenderRequest
+import androidx.credentials.PublicKeyCredential
+import androidx.credentials.provider.Action
+import androidx.credentials.provider.AuthenticationAction
+import androidx.credentials.provider.CredentialEntry
+import androidx.credentials.provider.CustomCredentialEntry
+import androidx.credentials.provider.PasswordCredentialEntry
+import androidx.credentials.provider.PublicKeyCredentialEntry
+import androidx.credentials.provider.RemoteEntry
+import com.android.credentialmanager.IS_AUTO_SELECTED_KEY
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.get.ProviderInfo
+import com.android.credentialmanager.model.get.RemoteEntryInfo
+import com.android.credentialmanager.TAG
+
+fun CredentialEntryInfo.getIntentSenderRequest(
+    isAutoSelected: Boolean = false
+): IntentSenderRequest? {
+    val entryIntent = fillInIntent?.putExtra(IS_AUTO_SELECTED_KEY, isAutoSelected)
+
+    return pendingIntent?.let{
+        IntentSenderRequest
+            .Builder(pendingIntent = it)
+            .setFillInIntent(entryIntent)
+            .build()
+    }
+}
+
+// Returns the list (potentially empty) of enabled provider.
+fun List<GetCredentialProviderData>.toProviderList(
+    context: Context,
+): List<ProviderInfo> {
+    val providerList: MutableList<ProviderInfo> = mutableListOf()
+    this.forEach {
+        val providerLabelAndIcon = getServiceLabelAndIcon(
+            context.packageManager,
+            it.providerFlattenedComponentName
+        ) ?: return@forEach
+        val (providerLabel, providerIcon) = providerLabelAndIcon
+        providerList.add(
+            ProviderInfo(
+                id = it.providerFlattenedComponentName,
+                icon = providerIcon,
+                displayName = providerLabel,
+                credentialEntryList = getCredentialOptionInfoList(
+                    providerId = it.providerFlattenedComponentName,
+                    providerLabel = providerLabel,
+                    credentialEntries = it.credentialEntries,
+                    context = context
+                ),
+                authenticationEntryList = getAuthenticationEntryList(
+                    it.providerFlattenedComponentName,
+                    providerLabel,
+                    providerIcon,
+                    it.authenticationEntries),
+                remoteEntry = getRemoteEntry(
+                    it.providerFlattenedComponentName,
+                    it.remoteEntry
+                ),
+                actionEntryList = getActionEntryList(
+                    it.providerFlattenedComponentName, it.actionChips, providerIcon
+                ),
+            )
+        )
+    }
+    return providerList
+}
+
+/**
+ * Note: caller required handle empty list due to parsing error.
+ */
+private fun getCredentialOptionInfoList(
+    providerId: String,
+    providerLabel: String,
+    credentialEntries: List<Entry>,
+    context: Context,
+): List<CredentialEntryInfo> {
+    val result: MutableList<CredentialEntryInfo> = mutableListOf()
+    credentialEntries.forEach {
+        val credentialEntry = it.slice.credentialEntry
+        when (credentialEntry) {
+            is PasswordCredentialEntry -> {
+                result.add(
+                    CredentialEntryInfo(
+                    providerId = providerId,
+                    providerDisplayName = providerLabel,
+                    entryKey = it.key,
+                    entrySubkey = it.subkey,
+                    pendingIntent = credentialEntry.pendingIntent,
+                    fillInIntent = it.frameworkExtrasIntent,
+                    credentialType = CredentialType.PASSWORD,
+                    credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
+                    userName = credentialEntry.username.toString(),
+                    displayName = credentialEntry.displayName?.toString(),
+                    icon = credentialEntry.icon.loadDrawable(context),
+                    shouldTintIcon = credentialEntry.isDefaultIcon,
+                    lastUsedTimeMillis = credentialEntry.lastUsedTime,
+                    isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
+                            credentialEntry.autoSelectAllowedFromOption,
+                )
+                )
+            }
+            is PublicKeyCredentialEntry -> {
+                result.add(
+                    CredentialEntryInfo(
+                    providerId = providerId,
+                    providerDisplayName = providerLabel,
+                    entryKey = it.key,
+                    entrySubkey = it.subkey,
+                    pendingIntent = credentialEntry.pendingIntent,
+                    fillInIntent = it.frameworkExtrasIntent,
+                    credentialType = CredentialType.PASSKEY,
+                    credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
+                    userName = credentialEntry.username.toString(),
+                    displayName = credentialEntry.displayName?.toString(),
+                    icon = credentialEntry.icon.loadDrawable(context),
+                    shouldTintIcon = credentialEntry.isDefaultIcon,
+                    lastUsedTimeMillis = credentialEntry.lastUsedTime,
+                    isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
+                            credentialEntry.autoSelectAllowedFromOption,
+                )
+                )
+            }
+            is CustomCredentialEntry -> {
+                result.add(
+                    CredentialEntryInfo(
+                    providerId = providerId,
+                    providerDisplayName = providerLabel,
+                    entryKey = it.key,
+                    entrySubkey = it.subkey,
+                    pendingIntent = credentialEntry.pendingIntent,
+                    fillInIntent = it.frameworkExtrasIntent,
+                    credentialType = CredentialType.UNKNOWN,
+                    credentialTypeDisplayName =
+                    credentialEntry.typeDisplayName?.toString().orEmpty(),
+                    userName = credentialEntry.title.toString(),
+                    displayName = credentialEntry.subtitle?.toString(),
+                    icon = credentialEntry.icon.loadDrawable(context),
+                    shouldTintIcon = credentialEntry.isDefaultIcon,
+                    lastUsedTimeMillis = credentialEntry.lastUsedTime,
+                    isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
+                            credentialEntry.autoSelectAllowedFromOption,
+                )
+                )
+            }
+            else -> Log.d(
+                TAG,
+                "Encountered unrecognized credential entry ${it.slice.spec?.type}"
+            )
+        }
+    }
+    return result
+}
+val Slice.credentialEntry: CredentialEntry?
+    get() =
+        try {
+            when (spec?.type) {
+                Credential.TYPE_PASSWORD_CREDENTIAL -> PasswordCredentialEntry.fromSlice(this)!!
+                PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
+                    PublicKeyCredentialEntry.fromSlice(this)!!
+
+                else -> CustomCredentialEntry.fromSlice(this)!!
+            }
+        } catch (e: Exception) {
+            // Try CustomCredentialEntry.fromSlice one last time in case the cause was a failed
+            // password / passkey parsing attempt.
+            CustomCredentialEntry.fromSlice(this)
+        }
+
+
+/**
+ * Note: caller required handle empty list due to parsing error.
+ */
+private fun getAuthenticationEntryList(
+    providerId: String,
+    providerDisplayName: String,
+    providerIcon: Drawable,
+    authEntryList: List<AuthenticationEntry>,
+): List<AuthenticationEntryInfo> {
+    val result: MutableList<AuthenticationEntryInfo> = mutableListOf()
+    authEntryList.forEach { entry ->
+        val structuredAuthEntry =
+            AuthenticationAction.fromSlice(entry.slice) ?: return@forEach
+
+        val title: String =
+            structuredAuthEntry.title.toString().ifEmpty { providerDisplayName }
+
+        result.add(
+            AuthenticationEntryInfo(
+            providerId = providerId,
+            entryKey = entry.key,
+            entrySubkey = entry.subkey,
+            pendingIntent = structuredAuthEntry.pendingIntent,
+            fillInIntent = entry.frameworkExtrasIntent,
+            title = title,
+            providerDisplayName = providerDisplayName,
+            icon = providerIcon,
+            isUnlockedAndEmpty = entry.status != AuthenticationEntry.STATUS_LOCKED,
+            isLastUnlocked =
+            entry.status == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT
+        )
+        )
+    }
+    return result
+}
+
+private fun getRemoteEntry(providerId: String, remoteEntry: Entry?): RemoteEntryInfo? {
+    if (remoteEntry == null) {
+        return null
+    }
+    val structuredRemoteEntry = RemoteEntry.fromSlice(remoteEntry.slice)
+        ?: return null
+    return RemoteEntryInfo(
+        providerId = providerId,
+        entryKey = remoteEntry.key,
+        entrySubkey = remoteEntry.subkey,
+        pendingIntent = structuredRemoteEntry.pendingIntent,
+        fillInIntent = remoteEntry.frameworkExtrasIntent,
+    )
+}
+
+/**
+ * Note: caller required handle empty list due to parsing error.
+ */
+private fun getActionEntryList(
+    providerId: String,
+    actionEntries: List<Entry>,
+    providerIcon: Drawable,
+): List<ActionEntryInfo> {
+    val result: MutableList<ActionEntryInfo> = mutableListOf()
+    actionEntries.forEach {
+        val actionEntryUi = Action.fromSlice(it.slice) ?: return@forEach
+        result.add(
+            ActionEntryInfo(
+            providerId = providerId,
+            entryKey = it.key,
+            entrySubkey = it.subkey,
+            pendingIntent = actionEntryUi.pendingIntent,
+            fillInIntent = it.frameworkExtrasIntent,
+            title = actionEntryUi.title.toString(),
+            icon = providerIcon,
+            subTitle = actionEntryUi.subtitle?.toString(),
+        )
+        )
+    }
+    return result
+}
+
+
+
+private fun getServiceLabelAndIcon(
+    pm: PackageManager,
+    providerFlattenedComponentName: String
+): Pair<String, Drawable>? {
+    var providerLabel: String? = null
+    var providerIcon: Drawable? = null
+    val component = ComponentName.unflattenFromString(providerFlattenedComponentName)
+    if (component == null) {
+        // Test data has only package name not component name.
+        // For test data usage only.
+        try {
+            val pkgInfo = if (Flags.instantAppsEnabled()) {
+                getPackageInfo(pm, providerFlattenedComponentName)
+            } else {
+                pm.getPackageInfo(
+                    providerFlattenedComponentName,
+                    PackageManager.PackageInfoFlags.of(0)
+                )
+            }
+            val applicationInfo = checkNotNull(pkgInfo.applicationInfo)
+            providerLabel =
+                applicationInfo.loadSafeLabel(
+                    pm, 0f,
+                    TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+                ).toString()
+            providerIcon = applicationInfo.loadIcon(pm)
+        } catch (e: Exception) {
+            Log.e(TAG, "Provider package info not found", e)
+        }
+    } else {
+        try {
+            val si = pm.getServiceInfo(component, PackageManager.ComponentInfoFlags.of(0))
+            providerLabel = si.loadSafeLabel(
+                pm, 0f,
+                TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+            ).toString()
+            providerIcon = si.loadIcon(pm)
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.e(TAG, "Provider service info not found", e)
+            // Added for mdoc use case where the provider may not need to register a service and
+            // instead only relies on the registration api.
+            try {
+                val pkgInfo = if (Flags.instantAppsEnabled()) {
+                    getPackageInfo(pm, providerFlattenedComponentName)
+                } else {
+                    pm.getPackageInfo(
+                        component.packageName,
+                        PackageManager.PackageInfoFlags.of(0)
+                    )
+                }
+                val applicationInfo = checkNotNull(pkgInfo.applicationInfo)
+                providerLabel =
+                    applicationInfo.loadSafeLabel(
+                        pm, 0f,
+                        TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+                    ).toString()
+                providerIcon = applicationInfo.loadIcon(pm)
+            } catch (e: Exception) {
+                Log.e(TAG, "Provider package info not found", e)
+            }
+        }
+    }
+    return if (providerLabel == null || providerIcon == null) {
+        Log.d(
+            TAG,
+            "Failed to load provider label/icon for provider $providerFlattenedComponentName"
+        )
+        null
+    } else {
+        Pair(providerLabel, providerIcon)
+    }
+}
+
+private fun getPackageInfo(
+    pm: PackageManager,
+    packageName: String
+): PackageInfo {
+    val packageManagerFlags = PackageManager.MATCH_INSTANT
+
+    return pm.getPackageInfo(
+        packageName,
+        PackageManager.PackageInfoFlags.of(
+            (packageManagerFlags).toLong())
+    )
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt
deleted file mode 100644
index 3471070..0000000
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt
+++ /dev/null
@@ -1,32 +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.0N
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ktx
-
-import androidx.activity.result.IntentSenderRequest
-import com.android.credentialmanager.IS_AUTO_SELECTED_KEY
-import com.android.credentialmanager.model.Password
-
-fun Password.getIntentSenderRequest(
-    isAutoSelected: Boolean = false
-): IntentSenderRequest {
-    val entryIntent = entry.frameworkExtrasIntent
-    entryIntent?.putExtra(IS_AUTO_SELECTED_KEY, isAutoSelected)
-
-    return IntentSenderRequest.Builder(
-        pendingIntent = passwordCredentialEntry.pendingIntent
-    ).setFillInIntent(entryIntent).build()
-}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
index d4bca2a..f1f1f7c 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
@@ -16,48 +16,18 @@
 
 package com.android.credentialmanager.mapper
 
+import android.content.Context
 import android.content.Intent
-import android.credentials.ui.Entry
-import androidx.credentials.provider.PasswordCredentialEntry
-import com.android.credentialmanager.factory.fromSlice
 import com.android.credentialmanager.ktx.getCredentialProviderDataList
 import com.android.credentialmanager.ktx.requestInfo
 import com.android.credentialmanager.ktx.resultReceiver
-import com.android.credentialmanager.model.Password
+import com.android.credentialmanager.ktx.toProviderList
 import com.android.credentialmanager.model.Request
-import com.google.common.collect.ImmutableList
-import com.google.common.collect.ImmutableMap
 
-fun Intent.toGet(): Request.Get {
-    val credentialEntries = mutableListOf<Pair<String, Entry>>()
-    for (providerData in getCredentialProviderDataList) {
-        for (credentialEntry in providerData.credentialEntries) {
-            credentialEntries.add(
-                Pair(providerData.providerFlattenedComponentName, credentialEntry)
-            )
-        }
-    }
-
-    val passwordEntries = mutableListOf<Password>()
-    for ((providerId, entry) in credentialEntries) {
-        val slice = fromSlice(entry.slice)
-        if (slice is PasswordCredentialEntry) {
-            passwordEntries.add(
-                Password(
-                    providerId = providerId,
-                    entry = entry,
-                    passwordCredentialEntry = slice
-                )
-            )
-        }
-    }
-
+fun Intent.toGet(context: Context): Request.Get {
     return Request.Get(
         token = requestInfo?.token,
-        resultReceiver = this.resultReceiver,
-        providers = ImmutableMap.copyOf(
-            getCredentialProviderDataList.associateBy { it.providerFlattenedComponentName }
-        ),
-        passwordEntries = ImmutableList.copyOf(passwordEntries)
+        resultReceiver = resultReceiver,
+        providerInfos = getCredentialProviderDataList.toProviderList(context)
     )
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/CredentialType.kt
similarity index 93%
rename from packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.kt
rename to packages/CredentialManager/shared/src/com/android/credentialmanager/model/CredentialType.kt
index cc92f60..3f85192 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/CredentialType.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.common
+package com.android.credentialmanager.model
 
 enum class CredentialType {
     UNKNOWN, PASSKEY, PASSWORD,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/EntryInfo.kt
similarity index 92%
rename from packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
rename to packages/CredentialManager/shared/src/com/android/credentialmanager/model/EntryInfo.kt
index ee36989..6d59f11 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/EntryInfo.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.common
+package com.android.credentialmanager.model
 
 import android.app.PendingIntent
 import android.content.Intent
 
-open class BaseEntry (
+open class EntryInfo (
     val providerId: String,
     val entryKey: String,
     val entrySubkey: String,
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt
deleted file mode 100644
index 2fe4fd5..0000000
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt
+++ /dev/null
@@ -1,26 +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.0N
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.model
-
-import android.credentials.ui.Entry
-import androidx.credentials.provider.PasswordCredentialEntry
-
-data class Password(
-    val providerId: String,
-    val entry: Entry,
-    val passwordCredentialEntry: PasswordCredentialEntry,
-)
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
index 2289ed7..7636462 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
@@ -16,11 +16,9 @@
 
 package com.android.credentialmanager.model
 
-import android.credentials.ui.ProviderData
 import android.os.IBinder
 import android.os.ResultReceiver
-import com.google.common.collect.ImmutableList
-import com.google.common.collect.ImmutableMap
+import com.android.credentialmanager.model.get.ProviderInfo
 
 /**
  * Represents the request made by the CredentialManager API.
@@ -51,8 +49,7 @@
     data class Get(
         override val token: IBinder?,
         val resultReceiver: ResultReceiver?,
-        val providers: ImmutableMap<String, ProviderData>,
-        val passwordEntries: ImmutableList<Password>,
+        val providerInfos: List<ProviderInfo>,
     ) : Request(token)
     /**
      * Request to start the create credentials flow.
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/CreateOptionInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/CreateOptionInfo.kt
new file mode 100644
index 0000000..d6189eb
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/CreateOptionInfo.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.credentialmanager.model.creation
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import com.android.credentialmanager.model.EntryInfo
+import java.time.Instant
+
+class CreateOptionInfo(
+    providerId: String,
+    entryKey: String,
+    entrySubkey: String,
+    pendingIntent: PendingIntent?,
+    fillInIntent: Intent?,
+    val userProviderDisplayName: String,
+    val profileIcon: Drawable?,
+    val passwordCount: Int?,
+    val passkeyCount: Int?,
+    val totalCredentialCount: Int?,
+    val lastUsedTime: Instant,
+    val footerDescription: String?,
+    val allowAutoSelect: Boolean,
+) : EntryInfo(
+    providerId,
+    entryKey,
+    entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = true,
+)
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/RemoteInfo.kt
similarity index 61%
copy from packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
copy to packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/RemoteInfo.kt
index ee36989..7ee50d7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/creation/RemoteInfo.kt
@@ -14,16 +14,23 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.common
+package com.android.credentialmanager.model.creation
 
 import android.app.PendingIntent
 import android.content.Intent
+import com.android.credentialmanager.model.EntryInfo
 
-open class BaseEntry (
-    val providerId: String,
-    val entryKey: String,
-    val entrySubkey: String,
-    val pendingIntent: PendingIntent?,
-    val fillInIntent: Intent?,
-    val shouldTerminateUiUponSuccessfulProviderResult: Boolean,
+class RemoteInfo(
+    providerId: String,
+    entryKey: String,
+    entrySubkey: String,
+    pendingIntent: PendingIntent?,
+    fillInIntent: Intent?,
+) : EntryInfo(
+    providerId,
+    entryKey,
+    entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = true,
 )
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ActionEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ActionEntryInfo.kt
new file mode 100644
index 0000000..d9eee86
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ActionEntryInfo.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.model.get
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import com.android.credentialmanager.model.EntryInfo
+
+class ActionEntryInfo(
+    providerId: String,
+    entryKey: String,
+    entrySubkey: String,
+    pendingIntent: PendingIntent?,
+    fillInIntent: Intent?,
+    val title: String,
+    val icon: Drawable,
+    val subTitle: String?,
+) : EntryInfo(
+    providerId,
+    entryKey,
+    entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = true,
+)
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/AuthenticationEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/AuthenticationEntryInfo.kt
new file mode 100644
index 0000000..01c394f
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/AuthenticationEntryInfo.kt
@@ -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 com.android.credentialmanager.model.get
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import com.android.credentialmanager.model.EntryInfo
+
+class AuthenticationEntryInfo(
+    providerId: String,
+    entryKey: String,
+    entrySubkey: String,
+    pendingIntent: PendingIntent?,
+    fillInIntent: Intent?,
+    val title: String,
+    val providerDisplayName: String,
+    val icon: Drawable,
+    // The entry had been unlocked and turned out to be empty. Used to determine whether to
+    // show "Tap to unlock" or "No sign-in info" for this entry.
+    val isUnlockedAndEmpty: Boolean,
+    // True if the entry was the last one unlocked. Used to show the no sign-in info snackbar.
+    val isLastUnlocked: Boolean,
+) : EntryInfo(
+    providerId,
+    entryKey, entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = false,
+)
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt
new file mode 100644
index 0000000..9725881
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.credentialmanager.model.get
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.EntryInfo
+import java.time.Instant
+
+class CredentialEntryInfo(
+    providerId: String,
+    entryKey: String,
+    entrySubkey: String,
+    pendingIntent: PendingIntent?,
+    fillInIntent: Intent?,
+    /** Type of this credential used for sorting. Not localized so must not be directly displayed. */
+    val credentialType: CredentialType,
+    /** Localized type value of this credential used for display purpose. */
+    val credentialTypeDisplayName: String,
+    val providerDisplayName: String,
+    val userName: String,
+    val displayName: String?,
+    val icon: Drawable?,
+    val shouldTintIcon: Boolean,
+    val lastUsedTimeMillis: Instant?,
+    val isAutoSelectable: Boolean,
+) : EntryInfo(
+    providerId,
+    entryKey,
+    entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = true,
+)
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ProviderInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ProviderInfo.kt
new file mode 100644
index 0000000..6da4146
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/ProviderInfo.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.credentialmanager.model.get
+
+import android.graphics.drawable.Drawable
+
+data class ProviderInfo(
+    /**
+     * Unique id (component name) of this provider.
+     * Not for display purpose - [displayName] should be used for ui rendering.
+     */
+    val id: String,
+    val icon: Drawable,
+    val displayName: String,
+    val credentialEntryList: List<CredentialEntryInfo>,
+    val authenticationEntryList: List<AuthenticationEntryInfo>,
+    val remoteEntry: RemoteEntryInfo?,
+    val actionEntryList: List<ActionEntryInfo>,
+)
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/RemoteEntryInfo.kt
similarity index 61%
copy from packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
copy to packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/RemoteEntryInfo.kt
index ee36989..a68bf74 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/RemoteEntryInfo.kt
@@ -14,16 +14,23 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.common
+package com.android.credentialmanager.model.get
 
 import android.app.PendingIntent
 import android.content.Intent
+import com.android.credentialmanager.model.EntryInfo
 
-open class BaseEntry (
-    val providerId: String,
-    val entryKey: String,
-    val entrySubkey: String,
-    val pendingIntent: PendingIntent?,
-    val fillInIntent: Intent?,
-    val shouldTerminateUiUponSuccessfulProviderResult: Boolean,
+class RemoteEntryInfo(
+    providerId: String,
+    entryKey: String,
+    entrySubkey: String,
+    pendingIntent: PendingIntent?,
+    fillInIntent: Intent?,
+) : EntryInfo(
+    providerId,
+    entryKey,
+    entrySubkey,
+    pendingIntent,
+    fillInIntent,
+    shouldTerminateUiUponSuccessfulProviderResult = true,
 )
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index bce86c4..6c5a984 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -28,7 +28,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.common.BaseEntry
+import com.android.credentialmanager.model.EntryInfo
 import com.android.credentialmanager.common.Constants
 import com.android.credentialmanager.common.DialogState
 import com.android.credentialmanager.common.ProviderActivityResult
@@ -47,7 +47,7 @@
 data class UiState(
     val createCredentialUiState: CreateCredentialUiState?,
     val getCredentialUiState: GetCredentialUiState?,
-    val selectedEntry: BaseEntry? = null,
+    val selectedEntry: EntryInfo? = null,
     val providerActivityState: ProviderActivityState = ProviderActivityState.NOT_APPLICABLE,
     val dialogState: DialogState = DialogState.ACTIVE,
     // True if the UI has one and only one auto selectable entry. Its provider activity will be
@@ -115,12 +115,13 @@
         launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
     ) {
         val entry = uiState.selectedEntry
-        if (entry != null && entry.pendingIntent != null) {
+        val pendingIntent = entry?.pendingIntent
+        if (pendingIntent != null) {
             Log.d(Constants.LOG_TAG, "Launching provider activity")
             uiState = uiState.copy(providerActivityState = ProviderActivityState.PENDING)
             val entryIntent = entry.fillInIntent
             entryIntent?.putExtra(Constants.IS_AUTO_SELECTED_KEY, uiState.isAutoSelectFlow)
-            val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
+            val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent)
                 .setFillInIntent(entryIntent).build()
             try {
                 launcher.launch(intentSenderRequest)
@@ -201,7 +202,7 @@
     /**************************************************************************/
     /*****                      Get Flow Callbacks                        *****/
     /**************************************************************************/
-    fun getFlowOnEntrySelected(entry: BaseEntry) {
+    fun getFlowOnEntrySelected(entry: EntryInfo) {
         Log.d(Constants.LOG_TAG, "credential selected: {provider=${entry.providerId}" +
             ", key=${entry.entryKey}, subkey=${entry.entrySubkey}}")
         uiState = if (entry.pendingIntent != null) {
@@ -363,7 +364,7 @@
         )
     }
 
-    fun createFlowOnEntrySelected(selectedEntry: BaseEntry) {
+    fun createFlowOnEntrySelected(selectedEntry: EntryInfo) {
         val providerId = selectedEntry.providerId
         val entryKey = selectedEntry.entryKey
         val entrySubkey = selectedEntry.entrySubkey
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index f0fa6c5..fc3970d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -16,13 +16,10 @@
 
 package com.android.credentialmanager
 
-import android.app.slice.Slice
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.PackageInfo
 import android.content.pm.PackageManager
-import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
-import android.credentials.ui.AuthenticationEntry
 import android.credentials.ui.CreateCredentialProviderData
 import android.credentials.ui.DisabledProviderData
 import android.credentials.ui.Entry
@@ -32,36 +29,26 @@
 import android.text.TextUtils
 import android.util.Log
 import com.android.credentialmanager.common.Constants
-import com.android.credentialmanager.common.CredentialType
+import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.createflow.ActiveEntry
 import com.android.credentialmanager.createflow.CreateCredentialUiState
-import com.android.credentialmanager.createflow.CreateOptionInfo
+import com.android.credentialmanager.model.creation.CreateOptionInfo
 import com.android.credentialmanager.createflow.CreateScreenState
 import com.android.credentialmanager.createflow.DisabledProviderInfo
 import com.android.credentialmanager.createflow.EnabledProviderInfo
-import com.android.credentialmanager.createflow.RemoteInfo
+import com.android.credentialmanager.model.creation.RemoteInfo
 import com.android.credentialmanager.createflow.RequestDisplayInfo
-import com.android.credentialmanager.getflow.ActionEntryInfo
-import com.android.credentialmanager.getflow.AuthenticationEntryInfo
-import com.android.credentialmanager.getflow.CredentialEntryInfo
-import com.android.credentialmanager.getflow.ProviderInfo
-import com.android.credentialmanager.getflow.RemoteEntryInfo
-import com.android.credentialmanager.getflow.TopBrandingContent
+import com.android.credentialmanager.model.get.ProviderInfo
+import com.android.credentialmanager.ktx.toProviderList
 import androidx.credentials.CreateCredentialRequest
 import androidx.credentials.CreateCustomCredentialRequest
 import androidx.credentials.CreatePasswordRequest
 import androidx.credentials.CreatePublicKeyCredentialRequest
-import androidx.credentials.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
-import androidx.credentials.provider.Action
-import androidx.credentials.provider.AuthenticationAction
 import androidx.credentials.provider.CreateEntry
-import androidx.credentials.provider.CredentialEntry
-import androidx.credentials.provider.CustomCredentialEntry
-import androidx.credentials.provider.PasswordCredentialEntry
-import androidx.credentials.provider.PublicKeyCredentialEntry
 import androidx.credentials.provider.RemoteEntry
 import org.json.JSONObject
 import android.credentials.flags.Flags
+import com.android.credentialmanager.getflow.TopBrandingContent
 import java.time.Instant
 
 
@@ -179,43 +166,7 @@
         fun toProviderList(
             providerDataList: List<GetCredentialProviderData>,
             context: Context,
-        ): List<ProviderInfo> {
-            val providerList: MutableList<ProviderInfo> = mutableListOf()
-            providerDataList.forEach {
-                val providerLabelAndIcon = getServiceLabelAndIcon(
-                    context.packageManager,
-                    it.providerFlattenedComponentName
-                ) ?: return@forEach
-                val (providerLabel, providerIcon) = providerLabelAndIcon
-                providerList.add(
-                    ProviderInfo(
-                        id = it.providerFlattenedComponentName,
-                        icon = providerIcon,
-                        displayName = providerLabel,
-                        credentialEntryList = getCredentialOptionInfoList(
-                            providerId = it.providerFlattenedComponentName,
-                            providerLabel = providerLabel,
-                            credentialEntries = it.credentialEntries,
-                            context = context
-                        ),
-                        authenticationEntryList = getAuthenticationEntryList(
-                            it.providerFlattenedComponentName,
-                            providerLabel,
-                            providerIcon,
-                            it.authenticationEntries),
-                        remoteEntry = getRemoteEntry(
-                            it.providerFlattenedComponentName,
-                            it.remoteEntry
-                        ),
-                        actionEntryList = getActionEntryList(
-                            it.providerFlattenedComponentName, it.actionChips, providerIcon
-                        ),
-                    )
-                )
-            }
-            return providerList
-        }
-
+        ): List<ProviderInfo> = providerDataList.toProviderList(context)
         fun toRequestDisplayInfo(
             requestInfo: RequestInfo?,
             context: Context,
@@ -254,178 +205,6 @@
                 preferTopBrandingContent = preferTopBrandingContent,
             )
         }
-
-
-        /**
-         * Note: caller required handle empty list due to parsing error.
-         */
-        private fun getCredentialOptionInfoList(
-            providerId: String,
-            providerLabel: String,
-            credentialEntries: List<Entry>,
-            context: Context,
-        ): List<CredentialEntryInfo> {
-            val result: MutableList<CredentialEntryInfo> = mutableListOf()
-            credentialEntries.forEach {
-                val credentialEntry = parseCredentialEntryFromSlice(it.slice)
-                when (credentialEntry) {
-                    is PasswordCredentialEntry -> {
-                        result.add(CredentialEntryInfo(
-                            providerId = providerId,
-                            providerDisplayName = providerLabel,
-                            entryKey = it.key,
-                            entrySubkey = it.subkey,
-                            pendingIntent = credentialEntry.pendingIntent,
-                            fillInIntent = it.frameworkExtrasIntent,
-                            credentialType = CredentialType.PASSWORD,
-                            credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
-                            userName = credentialEntry.username.toString(),
-                            displayName = credentialEntry.displayName?.toString(),
-                            icon = credentialEntry.icon.loadDrawable(context),
-                            shouldTintIcon = credentialEntry.isDefaultIcon,
-                            lastUsedTimeMillis = credentialEntry.lastUsedTime,
-                            isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
-                                credentialEntry.autoSelectAllowedFromOption,
-                        ))
-                    }
-                    is PublicKeyCredentialEntry -> {
-                        result.add(CredentialEntryInfo(
-                            providerId = providerId,
-                            providerDisplayName = providerLabel,
-                            entryKey = it.key,
-                            entrySubkey = it.subkey,
-                            pendingIntent = credentialEntry.pendingIntent,
-                            fillInIntent = it.frameworkExtrasIntent,
-                            credentialType = CredentialType.PASSKEY,
-                            credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
-                            userName = credentialEntry.username.toString(),
-                            displayName = credentialEntry.displayName?.toString(),
-                            icon = credentialEntry.icon.loadDrawable(context),
-                            shouldTintIcon = credentialEntry.isDefaultIcon,
-                            lastUsedTimeMillis = credentialEntry.lastUsedTime,
-                            isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
-                                credentialEntry.autoSelectAllowedFromOption,
-                        ))
-                    }
-                    is CustomCredentialEntry -> {
-                        result.add(CredentialEntryInfo(
-                            providerId = providerId,
-                            providerDisplayName = providerLabel,
-                            entryKey = it.key,
-                            entrySubkey = it.subkey,
-                            pendingIntent = credentialEntry.pendingIntent,
-                            fillInIntent = it.frameworkExtrasIntent,
-                            credentialType = CredentialType.UNKNOWN,
-                            credentialTypeDisplayName =
-                            credentialEntry.typeDisplayName?.toString().orEmpty(),
-                            userName = credentialEntry.title.toString(),
-                            displayName = credentialEntry.subtitle?.toString(),
-                            icon = credentialEntry.icon.loadDrawable(context),
-                            shouldTintIcon = credentialEntry.isDefaultIcon,
-                            lastUsedTimeMillis = credentialEntry.lastUsedTime,
-                            isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
-                                credentialEntry.autoSelectAllowedFromOption,
-                        ))
-                    }
-                    else -> Log.d(
-                        Constants.LOG_TAG,
-                        "Encountered unrecognized credential entry ${it.slice.spec?.type}"
-                    )
-                }
-            }
-            return result
-        }
-
-        /**
-         * @hide
-         */
-        fun parseCredentialEntryFromSlice(slice: Slice): CredentialEntry? {
-            try {
-                when (slice.spec?.type) {
-                    TYPE_PASSWORD_CREDENTIAL -> return PasswordCredentialEntry.fromSlice(slice)!!
-                    TYPE_PUBLIC_KEY_CREDENTIAL -> return PublicKeyCredentialEntry.fromSlice(slice)!!
-                    else -> return CustomCredentialEntry.fromSlice(slice)!!
-                }
-            } catch (e: Exception) {
-                // Try CustomCredentialEntry.fromSlice one last time in case the cause was a failed
-                // password / passkey parsing attempt.
-                return CustomCredentialEntry.fromSlice(slice)
-            }
-        }
-
-        /**
-         * Note: caller required handle empty list due to parsing error.
-         */
-        private fun getAuthenticationEntryList(
-            providerId: String,
-            providerDisplayName: String,
-            providerIcon: Drawable,
-            authEntryList: List<AuthenticationEntry>,
-        ): List<AuthenticationEntryInfo> {
-            val result: MutableList<AuthenticationEntryInfo> = mutableListOf()
-            authEntryList.forEach { entry ->
-                val structuredAuthEntry =
-                    AuthenticationAction.fromSlice(entry.slice) ?: return@forEach
-
-                val title: String =
-                    structuredAuthEntry.title.toString().ifEmpty { providerDisplayName }
-
-                result.add(AuthenticationEntryInfo(
-                    providerId = providerId,
-                    entryKey = entry.key,
-                    entrySubkey = entry.subkey,
-                    pendingIntent = structuredAuthEntry.pendingIntent,
-                    fillInIntent = entry.frameworkExtrasIntent,
-                    title = title,
-                    providerDisplayName = providerDisplayName,
-                    icon = providerIcon,
-                    isUnlockedAndEmpty = entry.status != AuthenticationEntry.STATUS_LOCKED,
-                    isLastUnlocked =
-                    entry.status == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT
-                ))
-            }
-            return result
-        }
-
-        private fun getRemoteEntry(providerId: String, remoteEntry: Entry?): RemoteEntryInfo? {
-            if (remoteEntry == null) {
-                return null
-            }
-            val structuredRemoteEntry = RemoteEntry.fromSlice(remoteEntry.slice)
-                ?: return null
-            return RemoteEntryInfo(
-                providerId = providerId,
-                entryKey = remoteEntry.key,
-                entrySubkey = remoteEntry.subkey,
-                pendingIntent = structuredRemoteEntry.pendingIntent,
-                fillInIntent = remoteEntry.frameworkExtrasIntent,
-            )
-        }
-
-        /**
-         * Note: caller required handle empty list due to parsing error.
-         */
-        private fun getActionEntryList(
-            providerId: String,
-            actionEntries: List<Entry>,
-            providerIcon: Drawable,
-        ): List<ActionEntryInfo> {
-            val result: MutableList<ActionEntryInfo> = mutableListOf()
-            actionEntries.forEach {
-                val actionEntryUi = Action.fromSlice(it.slice) ?: return@forEach
-                result.add(ActionEntryInfo(
-                    providerId = providerId,
-                    entryKey = it.key,
-                    entrySubkey = it.subkey,
-                    pendingIntent = actionEntryUi.pendingIntent,
-                    fillInIntent = it.frameworkExtrasIntent,
-                    title = actionEntryUi.title.toString(),
-                    icon = providerIcon,
-                    subTitle = actionEntryUi.subtitle?.toString(),
-                ))
-            }
-            return result
-        }
     }
 }
 
@@ -686,7 +465,8 @@
             val result: MutableList<CreateOptionInfo> = mutableListOf()
             creationEntries.forEach {
                 val createEntry = CreateEntry.fromSlice(it.slice) ?: return@forEach
-                result.add(CreateOptionInfo(
+                result.add(
+                    CreateOptionInfo(
                     providerId = providerId,
                     entryKey = it.key,
                     entrySubkey = it.subkey,
@@ -705,7 +485,8 @@
                         it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" +
                             "SELECT_ALLOWED")
                     }?.text == "true",
-                ))
+                )
+                )
             }
             return result.sortedWith(
                 compareByDescending { it.lastUsedTime }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 281696d..20d2f09 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -48,10 +48,11 @@
 import androidx.credentials.provider.PasswordCredentialEntry
 import androidx.credentials.provider.PublicKeyCredentialEntry
 import com.android.credentialmanager.GetFlowUtils
-import com.android.credentialmanager.getflow.CredentialEntryInfo
+import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.getflow.ProviderDisplayInfo
-import com.android.credentialmanager.getflow.ProviderInfo
+import com.android.credentialmanager.model.get.ProviderInfo
 import com.android.credentialmanager.getflow.toProviderDisplayInfo
+import com.android.credentialmanager.ktx.credentialEntry
 import org.json.JSONObject
 import java.util.concurrent.Executors
 
@@ -122,8 +123,7 @@
         val entryIconMap: MutableMap<String, Icon> = mutableMapOf()
         candidateProviderDataList.forEach { provider ->
             provider.credentialEntries.forEach { entry ->
-                val credentialEntry = GetFlowUtils.parseCredentialEntryFromSlice(entry.slice)
-                when (credentialEntry) {
+                when (val credentialEntry = entry.slice.credentialEntry) {
                     is PasswordCredentialEntry -> {
                         entryIconMap[entry.key + entry.subkey] = credentialEntry.icon
                     }
@@ -172,11 +172,11 @@
     }
 
     private fun processProvidersForAutofillId(
-            filLRequest: FillRequest,
-            autofillId: AutofillId,
-            providerList: List<ProviderInfo>,
-            entryIconMap: Map<String, Icon>,
-            fillResponseBuilder: FillResponse.Builder
+        filLRequest: FillRequest,
+        autofillId: AutofillId,
+        providerList: List<ProviderInfo>,
+        entryIconMap: Map<String, Icon>,
+        fillResponseBuilder: FillResponse.Builder
     ): Boolean {
         if (providerList.isEmpty()) {
             return false
@@ -200,7 +200,8 @@
         providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@ {
             val primaryEntry = it.sortedCredentialEntryList.first()
             val pendingIntent = primaryEntry.pendingIntent
-            if (pendingIntent == null || primaryEntry.fillInIntent == null) {
+            val fillInIntent = primaryEntry.fillInIntent
+            if (pendingIntent == null || fillInIntent == null) {
                 // FillInIntent will not be null because autofillId was retrieved from it.
                 Log.e(TAG, "PendingIntent was missing from the entry.")
                 return@usernameLoop
@@ -245,7 +246,7 @@
                                             presentationBuilder.build())
                                             .build())
                             .setAuthentication(pendingIntent.intentSender)
-                            .setAuthenticationExtras(primaryEntry.fillInIntent.extras)
+                            .setAuthenticationExtras(fillInIntent.extras)
                             .build())
             datasetAdded = true
         }
@@ -322,8 +323,8 @@
     }
 
     private fun copyProviderInfo(
-            providerInfo: ProviderInfo,
-            credentialList: List<CredentialEntryInfo>
+        providerInfo: ProviderInfo,
+        credentialList: List<CredentialEntryInfo>
     ): ProviderInfo {
         return ProviderInfo(
                 providerInfo.id,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index d45b6f6..14a9165 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -48,8 +48,8 @@
 import androidx.core.graphics.drawable.toBitmap
 import com.android.credentialmanager.CredentialSelectorViewModel
 import com.android.credentialmanager.R
-import com.android.credentialmanager.common.BaseEntry
-import com.android.credentialmanager.common.CredentialType
+import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.common.ProviderActivityState
 import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
 import com.android.credentialmanager.common.ui.ActionButton
@@ -68,6 +68,8 @@
 import com.android.credentialmanager.common.ui.PasskeyBenefitRow
 import com.android.credentialmanager.common.ui.HeadlineText
 import com.android.credentialmanager.logging.CreateCredentialEvent
+import com.android.credentialmanager.model.creation.CreateOptionInfo
+import com.android.credentialmanager.model.creation.RemoteInfo
 import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 
@@ -259,15 +261,15 @@
 
 @Composable
 fun MoreOptionsSelectionCard(
-        requestDisplayInfo: RequestDisplayInfo,
-        enabledProviderList: List<EnabledProviderInfo>,
-        disabledProviderList: List<DisabledProviderInfo>?,
-        sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
-        onBackCreationSelectionButtonSelected: () -> Unit,
-        onOptionSelected: (ActiveEntry) -> Unit,
-        onDisabledProvidersSelected: () -> Unit,
-        onRemoteEntrySelected: (BaseEntry) -> Unit,
-        onLog: @Composable (UiEventEnum) -> Unit,
+    requestDisplayInfo: RequestDisplayInfo,
+    enabledProviderList: List<EnabledProviderInfo>,
+    disabledProviderList: List<DisabledProviderInfo>?,
+    sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
+    onBackCreationSelectionButtonSelected: () -> Unit,
+    onOptionSelected: (ActiveEntry) -> Unit,
+    onDisabledProvidersSelected: () -> Unit,
+    onRemoteEntrySelected: (EntryInfo) -> Unit,
+    onLog: @Composable (UiEventEnum) -> Unit,
 ) {
     SheetContainerCard(topAppBar = {
         MoreOptionTopAppBar(
@@ -378,14 +380,14 @@
 
 @Composable
 fun CreationSelectionCard(
-        requestDisplayInfo: RequestDisplayInfo,
-        enabledProviderList: List<EnabledProviderInfo>,
-        providerInfo: EnabledProviderInfo,
-        createOptionInfo: CreateOptionInfo,
-        onOptionSelected: (BaseEntry) -> Unit,
-        onConfirm: () -> Unit,
-        onMoreOptionsSelected: () -> Unit,
-        onLog: @Composable (UiEventEnum) -> Unit,
+    requestDisplayInfo: RequestDisplayInfo,
+    enabledProviderList: List<EnabledProviderInfo>,
+    providerInfo: EnabledProviderInfo,
+    createOptionInfo: CreateOptionInfo,
+    onOptionSelected: (EntryInfo) -> Unit,
+    onConfirm: () -> Unit,
+    onMoreOptionsSelected: () -> Unit,
+    onLog: @Composable (UiEventEnum) -> Unit,
 ) {
     SheetContainerCard {
         item {
@@ -474,11 +476,11 @@
 
 @Composable
 fun ExternalOnlySelectionCard(
-        requestDisplayInfo: RequestDisplayInfo,
-        activeRemoteEntry: BaseEntry,
-        onOptionSelected: (BaseEntry) -> Unit,
-        onConfirm: () -> Unit,
-        onLog: @Composable (UiEventEnum) -> Unit,
+    requestDisplayInfo: RequestDisplayInfo,
+    activeRemoteEntry: EntryInfo,
+    onOptionSelected: (EntryInfo) -> Unit,
+    onConfirm: () -> Unit,
+    onLog: @Composable (UiEventEnum) -> Unit,
 ) {
     SheetContainerCard {
         item { HeadlineIcon(imageVector = Icons.Outlined.QrCodeScanner) }
@@ -575,17 +577,14 @@
 @Composable
 fun PrimaryCreateOptionRow(
     requestDisplayInfo: RequestDisplayInfo,
-    entryInfo: BaseEntry,
-    onOptionSelected: (BaseEntry) -> Unit
+    entryInfo: EntryInfo,
+    onOptionSelected: (EntryInfo) -> Unit
 ) {
     Entry(
         onClick = { onOptionSelected(entryInfo) },
-        iconImageBitmap =
-        if (entryInfo is CreateOptionInfo && entryInfo.profileIcon != null) {
-            entryInfo.profileIcon.toBitmap().asImageBitmap()
-        } else {
-            requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()
-        },
+        iconImageBitmap = ((entryInfo as? CreateOptionInfo)?.profileIcon
+            ?: requestDisplayInfo.typeIcon)
+            .toBitmap().asImageBitmap(),
         shouldApplyIconImageBitmapTint = !(entryInfo is CreateOptionInfo &&
             entryInfo.profileIcon != null),
         entryHeadlineText = requestDisplayInfo.title,
@@ -627,32 +626,33 @@
         entryThirdLineText =
         if (requestDisplayInfo.type == CredentialType.PASSKEY ||
             requestDisplayInfo.type == CredentialType.PASSWORD) {
-            if (createOptionInfo.passwordCount != null &&
-                createOptionInfo.passkeyCount != null
-            ) {
+            val passwordCount = createOptionInfo.passwordCount
+            val passkeyCount = createOptionInfo.passkeyCount
+            if (passwordCount != null && passkeyCount != null) {
                 stringResource(
                     R.string.more_options_usage_passwords_passkeys,
-                    createOptionInfo.passwordCount,
-                    createOptionInfo.passkeyCount
+                    passwordCount,
+                    passkeyCount
                 )
-            } else if (createOptionInfo.passwordCount != null) {
+            } else if (passwordCount != null) {
                 stringResource(
                     R.string.more_options_usage_passwords,
-                    createOptionInfo.passwordCount
+                    passwordCount
                 )
-            } else if (createOptionInfo.passkeyCount != null) {
+            } else if (passkeyCount != null) {
                 stringResource(
                     R.string.more_options_usage_passkeys,
-                    createOptionInfo.passkeyCount
+                    passkeyCount
                 )
             } else {
                 null
             }
         } else {
-            if (createOptionInfo.totalCredentialCount != null) {
+            val totalCredentialCount = createOptionInfo.totalCredentialCount
+            if (totalCredentialCount != null) {
                 stringResource(
                     R.string.more_options_usage_credentials,
-                    createOptionInfo.totalCredentialCount
+                    totalCredentialCount
                 )
             } else {
                 null
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index e9e8c2e..8b0ba87 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -16,22 +16,21 @@
 
 package com.android.credentialmanager.createflow
 
-import android.app.PendingIntent
-import android.content.Intent
 import android.graphics.drawable.Drawable
-import com.android.credentialmanager.common.BaseEntry
-import com.android.credentialmanager.common.CredentialType
-import java.time.Instant
+import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.creation.CreateOptionInfo
+import com.android.credentialmanager.model.creation.RemoteInfo
 
 data class CreateCredentialUiState(
-  val enabledProviders: List<EnabledProviderInfo>,
-  val disabledProviders: List<DisabledProviderInfo>? = null,
-  val currentScreenState: CreateScreenState,
-  val requestDisplayInfo: RequestDisplayInfo,
-  val sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
-  val activeEntry: ActiveEntry? = null,
-  val remoteEntry: RemoteInfo? = null,
-  val foundCandidateFromUserDefaultProvider: Boolean,
+    val enabledProviders: List<EnabledProviderInfo>,
+    val disabledProviders: List<DisabledProviderInfo>? = null,
+    val currentScreenState: CreateScreenState,
+    val requestDisplayInfo: RequestDisplayInfo,
+    val sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
+    val activeEntry: ActiveEntry? = null,
+    val remoteEntry: RemoteInfo? = null,
+    val foundCandidateFromUserDefaultProvider: Boolean,
 )
 
 internal fun isFlowAutoSelectable(
@@ -75,44 +74,6 @@
   displayName: String,
 ) : ProviderInfo(icon, id, displayName)
 
-class CreateOptionInfo(
-    providerId: String,
-    entryKey: String,
-    entrySubkey: String,
-    pendingIntent: PendingIntent?,
-    fillInIntent: Intent?,
-    val userProviderDisplayName: String,
-    val profileIcon: Drawable?,
-    val passwordCount: Int?,
-    val passkeyCount: Int?,
-    val totalCredentialCount: Int?,
-    val lastUsedTime: Instant,
-    val footerDescription: String?,
-    val allowAutoSelect: Boolean,
-) : BaseEntry(
-    providerId,
-    entryKey,
-    entrySubkey,
-    pendingIntent,
-    fillInIntent,
-    shouldTerminateUiUponSuccessfulProviderResult = true,
-)
-
-class RemoteInfo(
-  providerId: String,
-  entryKey: String,
-  entrySubkey: String,
-  pendingIntent: PendingIntent?,
-  fillInIntent: Intent?,
-) : BaseEntry(
-    providerId,
-    entryKey,
-    entrySubkey,
-    pendingIntent,
-    fillInIntent,
-    shouldTerminateUiUponSuccessfulProviderResult = true,
-)
-
 data class RequestDisplayInfo(
   val title: String,
   val subtitle: String?,
@@ -131,8 +92,8 @@
  * user selects a different entry on the more option page.
  */
 data class ActiveEntry (
-  val activeProvider: EnabledProviderInfo,
-  val activeEntryInfo: BaseEntry,
+    val activeProvider: EnabledProviderInfo,
+    val activeEntryInfo: EntryInfo,
 )
 
 /** The name of the current screen. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 72d030b..4ed84b9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -48,8 +48,9 @@
 import androidx.core.graphics.drawable.toBitmap
 import com.android.credentialmanager.CredentialSelectorViewModel
 import com.android.credentialmanager.R
-import com.android.credentialmanager.common.BaseEntry
-import com.android.credentialmanager.common.CredentialType
+import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.get.ProviderInfo
 import com.android.credentialmanager.common.ProviderActivityState
 import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
 import com.android.credentialmanager.common.ui.ActionButton
@@ -68,6 +69,10 @@
 import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
 import com.android.credentialmanager.common.ui.Snackbar
 import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.get.RemoteEntryInfo
 import com.android.credentialmanager.userAndDisplayNameForPasskey
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 
@@ -175,8 +180,8 @@
     requestDisplayInfo: RequestDisplayInfo,
     providerDisplayInfo: ProviderDisplayInfo,
     providerInfoList: List<ProviderInfo>,
-    activeEntry: BaseEntry?,
-    onEntrySelected: (BaseEntry) -> Unit,
+    activeEntry: EntryInfo?,
+    onEntrySelected: (EntryInfo) -> Unit,
     onConfirm: () -> Unit,
     onMoreOptionSelected: () -> Unit,
     onLog: @Composable (UiEventEnum) -> Unit,
@@ -358,7 +363,7 @@
 fun AllSignInOptionCard(
     providerInfoList: List<ProviderInfo>,
     providerDisplayInfo: ProviderDisplayInfo,
-    onEntrySelected: (BaseEntry) -> Unit,
+    onEntrySelected: (EntryInfo) -> Unit,
     onBackButtonClicked: () -> Unit,
     onCancel: () -> Unit,
     onLog: @Composable (UiEventEnum) -> Unit,
@@ -436,7 +441,7 @@
 @Composable
 fun ActionChips(
     providerInfoList: List<ProviderInfo>,
-    onEntrySelected: (BaseEntry) -> Unit,
+    onEntrySelected: (EntryInfo) -> Unit,
     isFirstSection: Boolean,
 ) {
     val actionChips = providerInfoList.flatMap { it.actionEntryList }
@@ -460,7 +465,7 @@
 @Composable
 fun RemoteEntryCard(
     remoteEntry: RemoteEntryInfo,
-    onEntrySelected: (BaseEntry) -> Unit,
+    onEntrySelected: (EntryInfo) -> Unit,
     isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
@@ -486,7 +491,7 @@
 @Composable
 fun LockedCredentials(
     authenticationEntryList: List<AuthenticationEntryInfo>,
-    onEntrySelected: (BaseEntry) -> Unit,
+    onEntrySelected: (EntryInfo) -> Unit,
     isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
@@ -508,7 +513,7 @@
 @Composable
 fun PerUserNameCredentials(
     perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
-    onEntrySelected: (BaseEntry) -> Unit,
+    onEntrySelected: (EntryInfo) -> Unit,
     isFirstSection: Boolean,
 ) {
     CredentialListSectionHeader(
@@ -532,7 +537,7 @@
 @Composable
 fun CredentialEntryRow(
     credentialEntryInfo: CredentialEntryInfo,
-    onEntrySelected: (BaseEntry) -> Unit,
+    onEntrySelected: (EntryInfo) -> Unit,
     enforceOneLine: Boolean = false,
     onTextLayout: (TextLayoutResult) -> Unit = {},
 ) {
@@ -571,7 +576,7 @@
 @Composable
 fun AuthenticationEntryRow(
     authenticationEntryInfo: AuthenticationEntryInfo,
-    onEntrySelected: (BaseEntry) -> Unit,
+    onEntrySelected: (EntryInfo) -> Unit,
     enforceOneLine: Boolean = false,
 ) {
     Entry(
@@ -596,7 +601,7 @@
 @Composable
 fun ActionEntryRow(
     actionEntryInfo: ActionEntryInfo,
-    onEntrySelected: (BaseEntry) -> Unit,
+    onEntrySelected: (EntryInfo) -> Unit,
 ) {
     ActionEntry(
         iconImageBitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 447a9d2..46bebc4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -16,21 +16,21 @@
 
 package com.android.credentialmanager.getflow
 
-import android.app.PendingIntent
-import android.content.Intent
 import android.graphics.drawable.Drawable
-import com.android.credentialmanager.common.BaseEntry
-import com.android.credentialmanager.common.CredentialType
+import com.android.credentialmanager.model.get.ProviderInfo
+import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.get.RemoteEntryInfo
 import com.android.internal.util.Preconditions
 
-import java.time.Instant
-
 data class GetCredentialUiState(
     val providerInfoList: List<ProviderInfo>,
     val requestDisplayInfo: RequestDisplayInfo,
     val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
     val currentScreenState: GetScreenState = toGetScreenState(providerDisplayInfo),
-    val activeEntry: BaseEntry? = toActiveEntry(providerDisplayInfo),
+    val activeEntry: EntryInfo? = toActiveEntry(providerDisplayInfo),
     val isNoAccount: Boolean = false,
 )
 
@@ -58,20 +58,6 @@
     return null
 }
 
-data class ProviderInfo(
-    /**
-     * Unique id (component name) of this provider.
-     * Not for display purpose - [displayName] should be used for ui rendering.
-     */
-    val id: String,
-    val icon: Drawable,
-    val displayName: String,
-    val credentialEntryList: List<CredentialEntryInfo>,
-    val authenticationEntryList: List<AuthenticationEntryInfo>,
-    val remoteEntry: RemoteEntryInfo?,
-    val actionEntryList: List<ActionEntryInfo>,
-)
-
 /** Display-centric data structure derived from the [ProviderInfo]. This abstraction is not grouping
  *  by the provider id but instead focuses on structures convenient for display purposes. */
 data class ProviderDisplayInfo(
@@ -84,87 +70,6 @@
     val remoteEntry: RemoteEntryInfo?
 )
 
-class CredentialEntryInfo(
-    providerId: String,
-    entryKey: String,
-    entrySubkey: String,
-    pendingIntent: PendingIntent?,
-    fillInIntent: Intent?,
-    /** Type of this credential used for sorting. Not localized so must not be directly displayed. */
-    val credentialType: CredentialType,
-    /** Localized type value of this credential used for display purpose. */
-    val credentialTypeDisplayName: String,
-    val providerDisplayName: String,
-    val userName: String,
-    val displayName: String?,
-    val icon: Drawable?,
-    val shouldTintIcon: Boolean,
-    val lastUsedTimeMillis: Instant?,
-    val isAutoSelectable: Boolean,
-) : BaseEntry(
-    providerId,
-    entryKey,
-    entrySubkey,
-    pendingIntent,
-    fillInIntent,
-    shouldTerminateUiUponSuccessfulProviderResult = true,
-)
-
-class AuthenticationEntryInfo(
-    providerId: String,
-    entryKey: String,
-    entrySubkey: String,
-    pendingIntent: PendingIntent?,
-    fillInIntent: Intent?,
-    val title: String,
-    val providerDisplayName: String,
-    val icon: Drawable,
-    // The entry had been unlocked and turned out to be empty. Used to determine whether to
-    // show "Tap to unlock" or "No sign-in info" for this entry.
-    val isUnlockedAndEmpty: Boolean,
-    // True if the entry was the last one unlocked. Used to show the no sign-in info snackbar.
-    val isLastUnlocked: Boolean,
-) : BaseEntry(
-    providerId,
-    entryKey, entrySubkey,
-    pendingIntent,
-    fillInIntent,
-    shouldTerminateUiUponSuccessfulProviderResult = false,
-)
-
-class RemoteEntryInfo(
-    providerId: String,
-    entryKey: String,
-    entrySubkey: String,
-    pendingIntent: PendingIntent?,
-    fillInIntent: Intent?,
-) : BaseEntry(
-    providerId,
-    entryKey,
-    entrySubkey,
-    pendingIntent,
-    fillInIntent,
-    shouldTerminateUiUponSuccessfulProviderResult = true,
-)
-
-class ActionEntryInfo(
-    providerId: String,
-    entryKey: String,
-    entrySubkey: String,
-    pendingIntent: PendingIntent?,
-    fillInIntent: Intent?,
-    val title: String,
-    val icon: Drawable,
-    val subTitle: String?,
-) : BaseEntry(
-    providerId,
-    entryKey,
-    entrySubkey,
-    pendingIntent,
-    fillInIntent,
-    shouldTerminateUiUponSuccessfulProviderResult = true,
-)
-
 data class RequestDisplayInfo(
     val appName: String,
     val preferImmediatelyAvailableCredentials: Boolean,
@@ -218,8 +123,8 @@
     val remoteEntryList = mutableListOf<RemoteEntryInfo>()
     providerInfoList.forEach { providerInfo ->
         authenticationEntryList.addAll(providerInfo.authenticationEntryList)
-        if (providerInfo.remoteEntry != null) {
-            remoteEntryList.add(providerInfo.remoteEntry)
+        providerInfo.remoteEntry?.let {
+            remoteEntryList.add(it)
         }
         // There can only be at most one remote entry
         Preconditions.checkState(remoteEntryList.size <= 1)
@@ -260,11 +165,11 @@
 
 private fun toActiveEntry(
     providerDisplayInfo: ProviderDisplayInfo,
-): BaseEntry? {
+): EntryInfo? {
     val sortedUserNameToCredentialEntryList =
         providerDisplayInfo.sortedUserNameToCredentialEntryList
     val authenticationEntryList = providerDisplayInfo.authenticationEntryList
-    var activeEntry: BaseEntry? = null
+    var activeEntry: EntryInfo? = null
     if (sortedUserNameToCredentialEntryList
             .size == 1 && authenticationEntryList.isEmpty()
     ) {
@@ -302,17 +207,18 @@
                 return 1
             }
         }
-
+        val p0LastUsedTimeMillis = p0.lastUsedTimeMillis
+        val p1LastUsedTimeMillis = p1.lastUsedTimeMillis
         // Then order by last used timestamp
-        if (p0.lastUsedTimeMillis != null && p1.lastUsedTimeMillis != null) {
-            if (p0.lastUsedTimeMillis < p1.lastUsedTimeMillis) {
+        if (p0LastUsedTimeMillis != null && p1LastUsedTimeMillis != null) {
+            if (p0LastUsedTimeMillis < p1LastUsedTimeMillis) {
                 return 1
-            } else if (p0.lastUsedTimeMillis > p1.lastUsedTimeMillis) {
+            } else if (p0LastUsedTimeMillis > p1LastUsedTimeMillis) {
                 return -1
             }
-        } else if (p0.lastUsedTimeMillis != null) {
+        } else if (p0LastUsedTimeMillis != null) {
             return -1
-        } else if (p1.lastUsedTimeMillis != null) {
+        } else if (p1LastUsedTimeMillis != null) {
             return 1
         }
         return 0
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt
index 6ededf3..d8a6019 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/AppModule.kt
@@ -1,29 +1,20 @@
 package com.android.credentialmanager.di
 
-import android.content.Context
-import android.content.pm.PackageManager
 import com.android.credentialmanager.client.CredentialManagerClient
 import com.android.credentialmanager.client.impl.CredentialManagerClientImpl
+import dagger.Binds
 import dagger.Module
-import dagger.Provides
 import dagger.hilt.InstallIn
-import dagger.hilt.android.qualifiers.ApplicationContext
 import dagger.hilt.components.SingletonComponent
 import javax.inject.Singleton
 
 @Module
 @InstallIn(SingletonComponent::class)
-internal object AppModule {
-    @Provides
+abstract class AppModule {
+    @Binds
     @Singleton
-    @JvmStatic
-    fun providePackageManager(@ApplicationContext context: Context): PackageManager =
-        context.packageManager
-
-    @Provides
-    @Singleton
-    @JvmStatic
-    fun provideCredentialManagerClient(packageManager: PackageManager): CredentialManagerClient =
-        CredentialManagerClientImpl(packageManager)
+    abstract fun provideCredentialManagerClient(
+        client: CredentialManagerClientImpl
+    ): CredentialManagerClient
 }
 
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index f2f878e..14b992a 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -23,8 +23,8 @@
     // TODO: b/301206470 returning a hard coded state for MVP
     if (true) return CredentialSelectorUiState.Get.SingleProviderSinglePassword
 
-    return if (providers.size == 1) {
-        if (passwordEntries.size == 1) {
+    return if (providerInfos.size == 1) {
+        if (providerInfos.first().credentialEntryList.size == 1) {
             CredentialSelectorUiState.Get.SingleProviderSinglePassword
         } else {
             TODO() // b/301206470 - Implement other get flows
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index c28df3e8..b64f581 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -76,7 +76,9 @@
             }
 
             SideEffect {
-                launcher.launch(state.intentSenderRequest)
+                state.intentSenderRequest?.let {
+                    launcher.launch(it)
+                }
             }
         }
 
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
index fb72c54..26bee1f 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
@@ -26,9 +26,9 @@
 import androidx.lifecycle.viewModelScope
 import com.android.credentialmanager.TAG
 import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Password
 import com.android.credentialmanager.model.Request
 import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.ui.model.PasswordUiModel
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -44,7 +44,7 @@
     private var initializeCalled = false
 
     private lateinit var requestGet: Request.Get
-    private lateinit var password: Password
+    private lateinit var entryInfo: CredentialEntryInfo
 
     private val _uiState =
         MutableStateFlow<SinglePasswordScreenUiState>(SinglePasswordScreenUiState.Idle)
@@ -63,14 +63,15 @@
                 _uiState.value = SinglePasswordScreenUiState.Error
             } else {
                 requestGet = request
-                if (requestGet.passwordEntries.isEmpty()) {
+
+                if (requestGet.providerInfos.all { it.credentialEntryList.isEmpty() }) {
                     Log.d(TAG, "Empty passwordEntries")
                     _uiState.value = SinglePasswordScreenUiState.Error
                 } else {
-                    password = requestGet.passwordEntries.first()
+                    entryInfo = requestGet.providerInfos.first().credentialEntryList.first()
                     _uiState.value = SinglePasswordScreenUiState.Loaded(
                         PasswordUiModel(
-                            email = password.passwordCredentialEntry.username.toString(),
+                            email = entryInfo.userName,
                         )
                     )
                 }
@@ -84,7 +85,7 @@
 
     fun onOKClick() {
         _uiState.value = SinglePasswordScreenUiState.PasswordSelected(
-            intentSenderRequest = password.getIntentSenderRequest()
+            intentSenderRequest = entryInfo.getIntentSenderRequest()
         )
     }
 
@@ -94,9 +95,9 @@
     ) {
         val userSelectionDialogResult = UserSelectionDialogResult(
             requestGet.token,
-            password.providerId,
-            password.entry.key,
-            password.entry.subkey,
+            entryInfo.providerId,
+            entryInfo.entryKey,
+            entryInfo.entrySubkey,
             if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
         )
         credentialManagerClient.sendResult(userSelectionDialogResult)
@@ -108,7 +109,7 @@
     data object Idle : SinglePasswordScreenUiState()
     data class Loaded(val passwordUiModel: PasswordUiModel) : SinglePasswordScreenUiState()
     data class PasswordSelected(
-        val intentSenderRequest: IntentSenderRequest
+        val intentSenderRequest: IntentSenderRequest?
     ) : SinglePasswordScreenUiState()
 
     data object Cancel : SinglePasswordScreenUiState()
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 38bd7d5..6213b34 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -49,6 +49,7 @@
         "androidx.fragment_fragment",
         "androidx.lifecycle_lifecycle-livedata",
         "androidx.lifecycle_lifecycle-extensions",
+        "android.content.pm.flags-aconfig-java",
     ],
 
     lint: {
@@ -75,6 +76,7 @@
         "androidx.fragment_fragment",
         "androidx.lifecycle_lifecycle-livedata",
         "androidx.lifecycle_lifecycle-extensions",
+        "android.content.pm.flags-aconfig-java",
     ],
     aaptflags: ["--product tablet"],
 
@@ -103,6 +105,7 @@
         "androidx.fragment_fragment",
         "androidx.lifecycle_lifecycle-livedata",
         "androidx.lifecycle_lifecycle-extensions",
+        "android.content.pm.flags-aconfig-java",
     ],
     aaptflags: ["--product tv"],
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 19d74b3..7b17cbd 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,8 +16,6 @@
 
 package com.android.packageinstaller;
 
-import static android.content.Intent.CATEGORY_LAUNCHER;
-
 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
 
 import android.app.Activity;
@@ -47,9 +45,6 @@
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         setResult(resultCode, data);
         finish();
-        if (data != null && data.hasCategory(CATEGORY_LAUNCHER)) {
-            startActivity(data);
-        }
     }
 
     @Override
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index e2107eb..4187058 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -16,12 +16,14 @@
 
 package com.android.packageinstaller;
 
+import static android.content.pm.Flags.usePiaV2;
 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
 
 import android.Manifest;
 import android.app.Activity;
 import android.app.DialogFragment;
 import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -57,14 +59,21 @@
 
     private final boolean mLocalLOGV = false;
 
-    // TODO (sumedhsen): Replace with an Android Feature Flag once implemented
-    private static final boolean USE_PIA_V2 = false;
-
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        if (USE_PIA_V2) {
+        mPackageManager = getPackageManager();
+        if (usePiaV2()) {
+            Log.i(TAG, "Using Pia V2");
+
+            mPackageManager.setComponentEnabledSetting(new ComponentName(this,
+                    com.android.packageinstaller.InstallEventReceiver.class),
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
+            mPackageManager.setComponentEnabledSetting(new ComponentName(this,
+                    com.android.packageinstaller.v2.model.InstallEventReceiver.class),
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+
             Intent piaV2 = new Intent(getIntent());
             piaV2.putExtra(InstallLaunch.EXTRA_CALLING_PKG_NAME, getCallingPackage());
             piaV2.putExtra(InstallLaunch.EXTRA_CALLING_PKG_UID, getLaunchedFromUid());
@@ -74,7 +83,6 @@
             finish();
             return;
         }
-        mPackageManager = getPackageManager();
         mUserManager = getSystemService(UserManager.class);
 
         Intent intent = getIntent();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 9af88c3b..215ead3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -120,7 +121,12 @@
         Button launchButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
         if (enabled) {
             launchButton.setOnClickListener(view -> {
-                setResult(Activity.RESULT_OK, mLaunchIntent);
+                try {
+                    startActivity(mLaunchIntent.addFlags(
+                        Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP));
+                } catch (ActivityNotFoundException | SecurityException e) {
+                    Log.e(LOG_TAG, "Could not start activity", e);
+                }
                 finish();
             });
         } else {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 34062a4..ba627e9 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -17,8 +17,8 @@
 package com.android.packageinstaller;
 
 import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.content.pm.Flags.usePiaV2;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-
 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
 
 import android.Manifest;
@@ -81,9 +81,6 @@
     private String mPackageName;
     private DialogInfo mDialogInfo;
 
-    // TODO (sumedhsen): Replace with an Android Feature Flag once implemented
-    private static final boolean USE_PIA_V2 = false;
-
     @Override
     public void onCreate(Bundle icicle) {
         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
@@ -92,7 +89,18 @@
         // be stale, if e.g. the app was uninstalled while the activity was destroyed.
         super.onCreate(null);
 
-        if (USE_PIA_V2 && !isTv()) {
+        if (usePiaV2() && !isTv()) {
+            Log.i(TAG, "Using Pia V2");
+
+            PackageManager pm = getPackageManager();
+            pm.setComponentEnabledSetting(
+                new ComponentName(this, com.android.packageinstaller.UninstallEventReceiver.class),
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
+            pm.setComponentEnabledSetting(
+                new ComponentName(this,
+                    com.android.packageinstaller.v2.model.UninstallEventReceiver.class),
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+
             boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
             Intent piaV2 = new Intent(getIntent());
             piaV2.putExtra(UninstallLaunch.EXTRA_CALLING_PKG_UID, getLaunchedFromUid());
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 4871ef3..010a6ce 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -17,7 +17,6 @@
     static_libs: [
         "androidx.preference_preference",
         "SettingsLibSettingsTheme",
-        "SettingsLibUtils",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml b/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml
index 4b3acbf..e70114f 100644
--- a/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml
+++ b/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml
@@ -17,5 +17,5 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.settingslib.widget.mainswitch">
-
+    <uses-sdk android:minSdkVersion="21" />
 </manifest>
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 2b5fcd8..e6f61a8 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
@@ -30,7 +32,6 @@
 
 import androidx.annotation.ColorInt;
 
-import com.android.settingslib.utils.BuildCompatUtils;
 import com.android.settingslib.widget.mainswitch.R;
 
 import java.util.ArrayList;
@@ -72,11 +73,18 @@
 
         LayoutInflater.from(context).inflate(R.layout.settingslib_main_switch_bar, this);
 
-        if (!BuildCompatUtils.isAtLeastS()) {
-            final TypedArray a = context.obtainStyledAttributes(
-                    new int[]{android.R.attr.colorAccent});
-            mBackgroundActivatedColor = a.getColor(0, 0);
-            mBackgroundColor = context.getColor(androidx.appcompat.R.color.material_grey_600);
+        if (Build.VERSION.SDK_INT < VERSION_CODES.S) {
+            TypedArray a;
+            if (Build.VERSION.SDK_INT >= VERSION_CODES.M) {
+                a = context.obtainStyledAttributes(
+                        new int[]{android.R.attr.colorAccent});
+                mBackgroundActivatedColor = a.getColor(0, 0);
+                mBackgroundColor = context.getColor(androidx.appcompat.R.color.material_grey_600);
+            } else {
+                a = context.obtainStyledAttributes(new int[]{android.R.attr.colorPrimary});
+                mBackgroundActivatedColor = a.getColor(0, 0);
+                mBackgroundColor = a.getColor(0, 0);
+            }
             a.recycle();
         }
 
@@ -148,7 +156,7 @@
      * Set icon space reserved for title
      */
     public void setIconSpaceReserved(boolean iconSpaceReserved) {
-        if (mTextView != null && !BuildCompatUtils.isAtLeastS()) {
+        if (mTextView != null && (Build.VERSION.SDK_INT < VERSION_CODES.S)) {
             LayoutParams params = (LayoutParams) mTextView.getLayoutParams();
             int iconSpace = getContext().getResources().getDimensionPixelSize(
                     R.dimen.settingslib_switchbar_subsettings_margin_start);
@@ -207,7 +215,7 @@
         mTextView.setEnabled(enabled);
         mSwitch.setEnabled(enabled);
 
-        if (BuildCompatUtils.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT >= VERSION_CODES.S) {
             mFrameView.setEnabled(enabled);
             mFrameView.setActivated(isChecked());
         }
@@ -222,7 +230,7 @@
     }
 
     private void setBackground(boolean isChecked) {
-        if (!BuildCompatUtils.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT < VERSION_CODES.S) {
             setBackgroundColor(isChecked ? mBackgroundActivatedColor : mBackgroundColor);
         } else {
             mFrameView.setActivated(isChecked);
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 80b944f..60bf48c 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -25,11 +25,11 @@
     alias(libs.plugins.kotlin.android) apply false
 }
 
-val androidTop : String = File(rootDir, "../../../../..").canonicalPath
+val androidTop: String = File(rootDir, "../../../../..").canonicalPath
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.6.0-alpha08"
+    extra["jetpackComposeVersion"] = "1.6.0-beta01"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 4335173..acd90f3 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
     api("androidx.slice:slice-builders:1.1.0-alpha02")
     api("androidx.slice:slice-core:1.1.0-alpha02")
     api("androidx.slice:slice-view:1.1.0-alpha02")
-    api("androidx.compose.material3:material3:1.2.0-alpha10")
+    api("androidx.compose.material3:material3:1.2.0-alpha11")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.7.5")
+    api("androidx.navigation:navigation-compose:2.7.4")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.7.0-alpha03")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index 26372b6..c395558 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -17,9 +17,7 @@
 package com.android.settingslib.spa.framework.theme
 
 import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material.ripple.LocalRippleTheme
-import androidx.compose.material.ripple.RippleAlpha
-import androidx.compose.material.ripple.RippleTheme
+import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
@@ -39,7 +37,7 @@
     MaterialTheme(colorScheme = colorScheme, typography = rememberSettingsTypography()) {
         CompositionLocalProvider(
             LocalColorScheme provides settingsColorScheme(isDarkTheme),
-            LocalRippleTheme provides SettingsRippleTheme,
+            LocalContentColor provides MaterialTheme.colorScheme.onSurface,
         ) {
             content()
         }
@@ -52,19 +50,3 @@
         @ReadOnlyComposable
         get() = LocalColorScheme.current
 }
-
-private object SettingsRippleTheme : RippleTheme {
-    @Composable
-    override fun defaultColor() = MaterialTheme.colorScheme.onSurface
-
-    @Composable
-    override fun rippleAlpha() = RippleAlpha
-}
-
-/** Alpha levels for all content. */
-private val RippleAlpha = RippleAlpha(
-    pressedAlpha = 0.48f,
-    focusedAlpha = 0.48f,
-    draggedAlpha = 0.32f,
-    hoveredAlpha = 0.16f,
-)
diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
index 0a2d9fc..e41126f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -140,12 +140,6 @@
         }
     }
 
-    @Override
-    public void setEnabled(boolean enabled) {
-        super.setEnabled(enabled);
-        setSwitchEnabled(enabled);
-    }
-
     @VisibleForTesting(otherwise = VisibleForTesting.NONE)
     public boolean isSwitchEnabled() {
         return mEnableSwitch;
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 8b0f19d..29ea25e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -22,7 +22,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -237,15 +236,13 @@
     }
 
     private void updateDisabledState() {
-        boolean isEnabled = !(mDisabledByAdmin || mDisabledByAppOps);
         if (!(mPreference instanceof RestrictedTopLevelPreference)) {
-            mPreference.setEnabled(isEnabled);
+            mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
         }
 
-        Drawable icon = mPreference.getIcon();
-        if (!isEnabled && icon != null) {
-            Utils.convertToGrayscale(icon);
-            mPreference.setIcon(icon);
+        if (mPreference instanceof PrimarySwitchPreference) {
+            ((PrimarySwitchPreference) mPreference)
+                    .setSwitchEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
         }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index f03263b..107d5f8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -728,14 +728,4 @@
         return false;
     }
 
-    /**
-     *  Convert a drawable to grayscale drawable
-     */
-    public static void convertToGrayscale(@NonNull Drawable drawable) {
-        ColorMatrix matrix = new ColorMatrix();
-        matrix.setSaturation(0.0f);
-
-        ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
-        drawable.setColorFilter(filter);
-    }
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index efed8c3..85e8769 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -235,6 +235,7 @@
                     Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR,
                     Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH,
                     Settings.Global.DEVICE_DEMO_MODE,
+                    Settings.Global.DEVICE_IDLE_CONSTANTS,
                     Settings.Global.DISABLE_WINDOW_BLURS,
                     Settings.Global.BATTERY_SAVER_CONSTANTS,
                     Settings.Global.BATTERY_TIP_CONSTANTS,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index d78038e..7cf562f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -242,184 +242,10 @@
 }
 
 filegroup {
-    name: "SystemUI-test-fakes",
-    srcs: [
-        /* Status bar fakes */
-        "tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt",
-
-        /* QS fakes */
-        "tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt",
-    ],
-    path: "tests/src",
-}
-
-filegroup {
-    name: "SystemUI-tests-robolectric-pilots",
-    srcs: [
-        /* Keyguard converted tests */
-        // data
-        "tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt",
-        "tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt",
-        // domain
-        "tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt",
-        "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt",
-        "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
-        "tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt",
-        "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
-        "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt",
-        "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt",
-        "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt",
-        "tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt",
-        // ui
-        "tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt",
-        "tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt",
-        "tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt",
-        "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt",
-        "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt",
-        "tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt",
-        "tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt",
-        // Keyguard helper
-        "tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt",
-        "tests/src/com/android/systemui/dock/DockManagerFake.java",
-        "tests/src/com/android/systemui/dump/LogBufferHelper.kt",
-        "tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java",
-
-        /* Biometric converted tests */
-        "tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt",
-        "tests/src/com/android/systemui/biometrics/AuthControllerTest.java",
-        "tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java",
-        "tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt",
-        "tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt",
-        "tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt",
-        "tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java",
-        "tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java",
-        "tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java",
-        "tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java",
-        "tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java",
-        "tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt",
-        "tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt",
-        "tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt",
-
-        /* Status bar wifi converted tests */
-        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt",
-
-        /* Bouncer UI tests */
-        "tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt",
-        "tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt",
-        "tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java",
-        "tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt",
-        "tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java",
-        "tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt",
-        "tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java",
-        "tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt",
-        "tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt",
-
-        /* Communal tests */
-        "tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt",
-        "tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt",
-        "tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt",
-
-        /* Dream tests */
-        "tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java",
-        "tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java",
-        "tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java",
-        "tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java",
-        "tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java",
-        "tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java",
-        "tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java",
-        "tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt",
-        "tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt",
-        "tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java",
-        "tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java",
-        "tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java",
-        "tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java",
-        "tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java",
-        "tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java",
-
-        /* Smartspace tests */
-        "tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt",
-        "tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt",
-        "tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt",
-        "tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt",
-
-        /* Quick Settings new pipeline converted tests */
-        "tests/src/com/android/systemui/qs/pipeline/data/**/*Test.kt",
-        "tests/src/com/android/systemui/qs/pipeline/domain/**/*Test.kt",
-        "tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt",
-        "tests/src/com/android/systemui/qs/tiles/base/**/*.kt",
-        "tests/src/com/android/systemui/qs/tiles/viewmodel/**/*.kt",
-        "tests/src/com/android/systemui/qs/tiles/impl/**/*.kt",
-
-        /* Authentication */
-        "tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt",
-        "tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt",
-
-        /* Device entry */
-        "tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt",
-        "tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt",
-
-        /* Bouncer scene */
-        "tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt",
-        "tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt",
-        "tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt",
-        "tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt",
-        "tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt",
-        "tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt",
-        "tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt",
-
-        /* Lockscreen scene */
-        "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt",
-
-        /* Shade scene */
-        "tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt",
-        "tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt",
-
-        /* Quick Settings scene */
-        "tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt",
-
-        /* Flexiglass / Scene framework tests */
-        "tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt",
-        "tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt",
-        "tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt",
-        "tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt",
-        "tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt",
-        "tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt",
-        "tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt",
-
-    ],
-    path: "tests/src",
-}
-
-filegroup {
     name: "SystemUI-tests-multivalent",
     srcs: [
         "multivalentTests/src/**/*.kt",
+        "multivalentTests/src/**/*.java",
     ],
     path: "multivalentTests/src",
 }
@@ -597,8 +423,6 @@
         "tests/robolectric/src/**/*.kt",
         "tests/robolectric/src/**/*.java",
         ":SystemUI-tests-utils",
-        ":SystemUI-test-fakes",
-        ":SystemUI-tests-robolectric-pilots",
         ":SystemUI-tests-multivalent",
     ],
     static_libs: [
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0480b9d..0c89a5d 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -126,24 +126,6 @@
           "exclude-annotation": "android.platform.test.annotations.Postsubmit"
         }
       ]
-    },
-    {
-      // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
-      "name": "SystemUIGoogleBiometricsScreenshotTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.Postsubmit"
-        }
-      ]
     }
   ],
   
@@ -170,18 +152,6 @@
           "include-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
-    },
-    {
-      // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
-      "name": "SystemUIGoogleBiometricsScreenshotTests",
-      "options": [
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        },
-        {
-          "include-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
     }
   ]
 }
diff --git a/packages/SystemUI/aconfig/OWNERS b/packages/SystemUI/aconfig/OWNERS
index e1a7a0f..902ba90 100644
--- a/packages/SystemUI/aconfig/OWNERS
+++ b/packages/SystemUI/aconfig/OWNERS
@@ -1 +1,2 @@
 per-file accessibility.aconfig = file:/core/java/android/view/accessibility/OWNERS
+per-file biometrics_framework.aconfig = file:/services/core/java/com/android/server/biometrics/OWNERS
diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig
new file mode 100644
index 0000000..5fd3b48
--- /dev/null
+++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.systemui"
+
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
+flag {
+    name: "bp_talkback"
+    namespace: "biometrics_framework"
+    description: "Adds talkback directional guidance when using UDFPS with biometric prompt"
+    bug: "310044658"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 1762065..3e84597 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -164,3 +164,9 @@
    bug: "296122467"
 }
 
+flag {
+   name: "record_issue_qs_tile"
+   namespace: "systemui"
+   description: "Replace Record Trace QS Tile with expanded Record Issue QS Tile"
+   bug: "305049544"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 0b13383..eb06889 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -16,12 +16,10 @@
 
 package com.android.systemui.bouncer.ui.composable
 
+import android.view.ViewTreeObserver
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.imeAnimationTarget
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material3.LocalTextStyle
@@ -30,46 +28,56 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
+import androidx.core.view.WindowInsetsCompat
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 
 /** UI for the input part of a password-requiring version of the bouncer. */
-@OptIn(ExperimentalLayoutApi::class)
 @Composable
 internal fun PasswordBouncer(
     viewModel: PasswordBouncerViewModel,
     modifier: Modifier = Modifier,
 ) {
     val focusRequester = remember { FocusRequester() }
+    val isTextFieldFocusRequested by viewModel.isTextFieldFocusRequested.collectAsState()
+    LaunchedEffect(isTextFieldFocusRequested) {
+        if (isTextFieldFocusRequested) {
+            focusRequester.requestFocus()
+        }
+    }
+    val (isTextFieldFocused, onTextFieldFocusChanged) = remember { mutableStateOf(false) }
+    LaunchedEffect(isTextFieldFocused) {
+        viewModel.onTextFieldFocusChanged(isFocused = isTextFieldFocused)
+    }
+
     val password: String by viewModel.password.collectAsState()
     val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
     val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
 
-    val density = LocalDensity.current
-    val isImeVisible by rememberUpdatedState(WindowInsets.imeAnimationTarget.getBottom(density) > 0)
+    val isImeVisible by isSoftwareKeyboardVisible()
     LaunchedEffect(isImeVisible) { viewModel.onImeVisibilityChanged(isImeVisible) }
 
     DisposableEffect(Unit) {
         viewModel.onShown()
-
-        // When the UI comes up, request focus on the TextField to bring up the software keyboard.
-        focusRequester.requestFocus()
-
         onDispose { viewModel.onHidden() }
     }
 
@@ -104,16 +112,39 @@
                     onDone = { viewModel.onAuthenticateKeyPressed() },
                 ),
             modifier =
-                Modifier.focusRequester(focusRequester).drawBehind {
-                    drawLine(
-                        color = color,
-                        start = Offset(x = 0f, y = size.height - lineWidthPx),
-                        end = Offset(size.width, y = size.height - lineWidthPx),
-                        strokeWidth = lineWidthPx,
-                    )
-                },
+                Modifier.focusRequester(focusRequester)
+                    .onFocusChanged { onTextFieldFocusChanged(it.isFocused) }
+                    .drawBehind {
+                        drawLine(
+                            color = color,
+                            start = Offset(x = 0f, y = size.height - lineWidthPx),
+                            end = Offset(size.width, y = size.height - lineWidthPx),
+                            strokeWidth = lineWidthPx,
+                        )
+                    },
         )
 
         Spacer(Modifier.height(100.dp))
     }
 }
+
+/** Returns a [State] with `true` when the IME/keyboard is visible and `false` when it's not. */
+@Composable
+fun isSoftwareKeyboardVisible(): State<Boolean> {
+    val view = LocalView.current
+    val viewTreeObserver = view.viewTreeObserver
+
+    return produceState(
+        initialValue = false,
+        key1 = viewTreeObserver,
+    ) {
+        val listener =
+            ViewTreeObserver.OnGlobalLayoutListener {
+                value = view.rootWindowInsets?.isVisible(WindowInsetsCompat.Type.ime()) ?: false
+            }
+
+        viewTreeObserver.addOnGlobalLayoutListener(listener)
+
+        awaitDispose { viewTreeObserver.removeOnGlobalLayoutListener(listener) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 1e80732..83fb17f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -319,10 +319,10 @@
     @Test
     fun imeHiddenEvent_isTriggered() =
         testScope.runTest {
-            val imeHiddenEvent by collectLastValue(underTest.onImeHidden)
+            val imeHiddenEvent by collectLastValue(underTest.onImeHiddenByUser)
             runCurrent()
 
-            underTest.onImeHidden()
+            underTest.onImeHiddenByUser()
             runCurrent()
 
             assertThat(imeHiddenEvent).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
similarity index 82%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 63c992b..45c186d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -23,8 +23,6 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runTest
@@ -76,18 +74,4 @@
             underTest.onAuthenticateButtonClicked()
             assertThat(animateFailure).isFalse()
         }
-
-    @Test
-    fun onImeVisibilityChanged() =
-        testScope.runTest {
-            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "")
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "")
-            val onImeHidden by collectLastValue(bouncerInteractor.onImeHidden)
-
-            underTest.onImeVisibilityChanged(true)
-            assertThat(onImeHidden).isNull()
-
-            underTest.onImeVisibilityChanged(false)
-            assertThat(onImeHidden).isNotNull()
-        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
similarity index 65%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 9b1e958..937c703 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -20,7 +20,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
@@ -43,7 +45,11 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val authenticationInteractor = utils.authenticationInteractor()
+    private val authenticationRepository = utils.authenticationRepository
+    private val authenticationInteractor =
+        utils.authenticationInteractor(
+            repository = authenticationRepository,
+        )
     private val sceneInteractor = utils.sceneInteractor()
     private val bouncerInteractor =
         utils.bouncerInteractor(
@@ -207,6 +213,101 @@
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
+    @Test
+    fun onImeVisibilityChanged_false_doesNothing() =
+        testScope.runTest {
+            val events by collectValues(bouncerInteractor.onImeHiddenByUser)
+            assertThat(events).isEmpty()
+
+            underTest.onImeVisibilityChanged(isVisible = false)
+            assertThat(events).isEmpty()
+        }
+
+    @Test
+    fun onImeVisibilityChanged_falseAfterTrue_emitsOnImeHiddenByUserEvent() =
+        testScope.runTest {
+            val events by collectValues(bouncerInteractor.onImeHiddenByUser)
+            assertThat(events).isEmpty()
+
+            underTest.onImeVisibilityChanged(isVisible = true)
+            assertThat(events).isEmpty()
+
+            underTest.onImeVisibilityChanged(isVisible = false)
+            assertThat(events).hasSize(1)
+
+            underTest.onImeVisibilityChanged(isVisible = true)
+            assertThat(events).hasSize(1)
+
+            underTest.onImeVisibilityChanged(isVisible = false)
+            assertThat(events).hasSize(2)
+        }
+
+    @Test
+    fun onImeVisibilityChanged_falseAfterTrue_whileThrottling_doesNothing() =
+        testScope.runTest {
+            val events by collectValues(bouncerInteractor.onImeHiddenByUser)
+            assertThat(events).isEmpty()
+            underTest.onImeVisibilityChanged(isVisible = true)
+            setThrottling(true)
+
+            underTest.onImeVisibilityChanged(isVisible = false)
+
+            assertThat(events).isEmpty()
+        }
+
+    @Test
+    fun isTextFieldFocusRequested_initiallyTrue() =
+        testScope.runTest {
+            val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
+            assertThat(isTextFieldFocusRequested).isTrue()
+        }
+
+    @Test
+    fun isTextFieldFocusRequested_focusGained_becomesFalse() =
+        testScope.runTest {
+            val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
+
+            underTest.onTextFieldFocusChanged(isFocused = true)
+
+            assertThat(isTextFieldFocusRequested).isFalse()
+        }
+
+    @Test
+    fun isTextFieldFocusRequested_focusLost_becomesTrue() =
+        testScope.runTest {
+            val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
+            underTest.onTextFieldFocusChanged(isFocused = true)
+
+            underTest.onTextFieldFocusChanged(isFocused = false)
+
+            assertThat(isTextFieldFocusRequested).isTrue()
+        }
+
+    @Test
+    fun isTextFieldFocusRequested_focusLostWhileThrottling_staysFalse() =
+        testScope.runTest {
+            val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
+            underTest.onTextFieldFocusChanged(isFocused = true)
+            setThrottling(true)
+
+            underTest.onTextFieldFocusChanged(isFocused = false)
+
+            assertThat(isTextFieldFocusRequested).isFalse()
+        }
+
+    @Test
+    fun isTextFieldFocusRequested_throttlingCountdownEnds_becomesTrue() =
+        testScope.runTest {
+            val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
+            underTest.onTextFieldFocusChanged(isFocused = true)
+            setThrottling(true)
+            underTest.onTextFieldFocusChanged(isFocused = false)
+
+            setThrottling(false)
+
+            assertThat(isTextFieldFocusRequested).isTrue()
+        }
+
     private fun TestScope.switchToScene(toScene: SceneKey) {
         val currentScene by collectLastValue(sceneInteractor.desiredScene)
         val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer
@@ -226,6 +327,35 @@
         switchToScene(SceneKey.Bouncer)
     }
 
+    private suspend fun TestScope.setThrottling(
+        isThrottling: Boolean,
+        failedAttemptCount: Int = 5,
+    ) {
+        if (isThrottling) {
+            repeat(failedAttemptCount) {
+                authenticationRepository.reportAuthenticationAttempt(false)
+            }
+            val remainingTimeMs = 30_000
+            authenticationRepository.setThrottleDuration(remainingTimeMs)
+            authenticationRepository.setThrottling(
+                AuthenticationThrottlingModel(
+                    failedAttemptCount = failedAttemptCount,
+                    remainingMs = remainingTimeMs,
+                )
+            )
+        } else {
+            authenticationRepository.reportAuthenticationAttempt(true)
+            authenticationRepository.setThrottling(
+                AuthenticationThrottlingModel(
+                    failedAttemptCount = failedAttemptCount,
+                    remainingMs = 0,
+                )
+            )
+        }
+
+        runCurrent()
+    }
+
     companion object {
         private const val ENTER_YOUR_PASSWORD = "Enter your password"
         private const val WRONG_PASSWORD = "Wrong password"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 0004f52..910097e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
@@ -56,6 +57,13 @@
         )
 
     @Test
+    fun canSwipeToEnter_startsNull() =
+        testScope.runTest {
+            val values by collectValues(underTest.canSwipeToEnter)
+            assertThat(values[0]).isNull()
+        }
+
+    @Test
     fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
         testScope.runTest {
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/LogBufferHelper.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dump/LogBufferHelper.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/CustomTileDefaultsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/CustomTileDefaultsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/CustomTileDefaultsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/CustomTileDefaultsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/CustomTilePackageUpdatesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/CustomTilePackageUpdatesRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/custom/CustomTilePackageUpdatesRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/CustomTilePackageUpdatesRepositoryTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
new file mode 100644
index 0000000..cf076c5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.data.repository
+
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class CustomTileRepositoryTest : SysuiTestCase() {
+
+    private val testScope = TestScope()
+
+    private val persister = FakeCustomTileStatePersister()
+
+    private val underTest: CustomTileRepository =
+        CustomTileRepositoryImpl(
+            TileSpec.create(TEST_COMPONENT),
+            persister,
+            testScope.testScheduler,
+        )
+
+    @Test
+    fun persistableTileIsRestoredForUser() =
+        testScope.runTest {
+            persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+            persister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
+
+            underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+            runCurrent()
+
+            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+        }
+
+    @Test
+    fun notPersistableTileIsNotRestored() =
+        testScope.runTest {
+            persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+            val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+
+            underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
+            runCurrent()
+
+            assertThat(tiles()).isEmpty()
+        }
+
+    @Test
+    fun emptyPersistedStateIsHandled() =
+        testScope.runTest {
+            val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+
+            underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+            runCurrent()
+
+            assertThat(tiles()).isEmpty()
+        }
+
+    @Test
+    fun updatingWithPersistableTilePersists() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+        }
+
+    @Test
+    fun updatingWithNotPersistableTileDoesntPersist() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
+            runCurrent()
+
+            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+        }
+
+    @Test
+    fun updateWithTileEmits() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+        }
+
+    @Test
+    fun updatingPeristableWithDefaultsPersists() =
+        testScope.runTest {
+            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+            runCurrent()
+
+            assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+        }
+
+    @Test
+    fun updatingNotPersistableWithDefaultsDoesntPersist() =
+        testScope.runTest {
+            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
+            runCurrent()
+
+            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+        }
+
+    @Test
+    fun updatingPeristableWithErrorDefaultsDoesntPersist() =
+        testScope.runTest {
+            underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
+            runCurrent()
+
+            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+        }
+
+    @Test
+    fun updateWithDefaultsEmits() =
+        testScope.runTest {
+            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+            runCurrent()
+
+            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+        }
+
+    @Test
+    fun getTileForAnotherUserReturnsNull() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            assertThat(underTest.getTile(TEST_USER_2)).isNull()
+        }
+
+    @Test
+    fun getTilesForAnotherUserEmpty() =
+        testScope.runTest {
+            val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            assertThat(tiles()).isEmpty()
+        }
+
+    @Test
+    fun updatingWithTileForTheSameUserAddsData() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
+            runCurrent()
+
+            val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+        }
+
+    @Test
+    fun updatingWithTileForAnotherUserOverridesTile() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+            runCurrent()
+
+            val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+            underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
+            runCurrent()
+
+            assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+            assertThat(tiles()).hasSize(1)
+            assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+        }
+
+    @Test
+    fun updatingWithDefaultsForTheSameUserAddsData() =
+        testScope.runTest {
+            underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
+            runCurrent()
+
+            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+            runCurrent()
+
+            val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+            assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+            assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+        }
+
+    @Test
+    fun updatingWithDefaultsForAnotherUserOverridesTile() =
+        testScope.runTest {
+            underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+            runCurrent()
+
+            val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+            underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
+            runCurrent()
+
+            assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+            assertThat(tiles()).hasSize(1)
+            assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+        }
+
+    private companion object {
+
+        val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+
+        val TEST_USER_1 = UserHandle.of(1)!!
+        val TEST_TILE_1 =
+            Tile().apply {
+                label = "test_tile_1"
+                icon = Icon.createWithContentUri("file://test_1")
+            }
+        val TEST_TILE_KEY_1 = TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier)
+        val TEST_DEFAULTS_1 = CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label)
+
+        val TEST_USER_2 = UserHandle.of(2)!!
+        val TEST_TILE_2 =
+            Tile().apply {
+                label = "test_tile_2"
+                icon = Icon.createWithContentUri("file://test_2")
+            }
+        val TEST_TILE_KEY_2 = TileServiceKey(TEST_COMPONENT, TEST_USER_2.identifier)
+        val TEST_DEFAULTS_2 = CustomTileDefaults.Result(TEST_TILE_2.icon, TEST_TILE_2.label)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
new file mode 100644
index 0000000..eebb145
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.domain.interactor
+
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import android.text.format.DateUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.external.TileServiceManager
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class CustomTileInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var tileServiceManager: TileServiceManager
+
+    private val testScope = TestScope()
+
+    private val defaultsRepository = FakeCustomTileDefaultsRepository()
+    private val customTileStatePersister = FakeCustomTileStatePersister()
+    private val customTileRepository =
+        FakeCustomTileRepository(
+            TEST_TILE_SPEC,
+            customTileStatePersister,
+            testScope.testScheduler,
+        )
+
+    private lateinit var underTest: CustomTileInteractor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            CustomTileInteractor(
+                TEST_USER,
+                defaultsRepository,
+                customTileRepository,
+                tileServiceManager,
+                testScope.backgroundScope,
+                testScope.testScheduler,
+            )
+    }
+
+    @Test
+    fun activeTileIsAvailableAfterRestored() =
+        testScope.runTest {
+            whenever(tileServiceManager.isActiveTile).thenReturn(true)
+            customTileStatePersister.persistState(
+                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                TEST_TILE,
+            )
+
+            underTest.init()
+
+            assertThat(underTest.tile).isEqualTo(TEST_TILE)
+            assertThat(underTest.tiles.first()).isEqualTo(TEST_TILE)
+        }
+
+    @Test
+    fun notActiveTileIsAvailableAfterUpdated() =
+        testScope.runTest {
+            whenever(tileServiceManager.isActiveTile).thenReturn(false)
+            customTileStatePersister.persistState(
+                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                TEST_TILE,
+            )
+            val tiles = collectValues(underTest.tiles)
+            val initJob = launch { underTest.init() }
+
+            underTest.updateTile(TEST_TILE)
+            runCurrent()
+            initJob.join()
+
+            assertThat(tiles()).hasSize(1)
+            assertThat(tiles().last()).isEqualTo(TEST_TILE)
+        }
+
+    @Test
+    fun notActiveTileIsAvailableAfterDefaultsUpdated() =
+        testScope.runTest {
+            whenever(tileServiceManager.isActiveTile).thenReturn(false)
+            customTileStatePersister.persistState(
+                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                TEST_TILE,
+            )
+            val tiles = collectValues(underTest.tiles)
+            val initJob = launch { underTest.init() }
+
+            defaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
+            defaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
+            runCurrent()
+            initJob.join()
+
+            assertThat(tiles()).hasSize(1)
+            assertThat(tiles().last()).isEqualTo(TEST_TILE)
+        }
+
+    @Test(expected = IllegalStateException::class)
+    fun getTileBeforeInitThrows() = testScope.runTest { underTest.tile }
+
+    @Test
+    fun initSuspendsForActiveTileNotRestoredAndNotUpdated() =
+        testScope.runTest {
+            whenever(tileServiceManager.isActiveTile).thenReturn(true)
+            val tiles = collectValues(underTest.tiles)
+
+            val initJob = backgroundScope.launch { underTest.init() }
+            advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+
+            // Is still suspended
+            assertThat(initJob.isActive).isTrue()
+            assertThat(tiles()).isEmpty()
+        }
+
+    @Test
+    fun initSuspendedForNotActiveTileWithoutUpdates() =
+        testScope.runTest {
+            whenever(tileServiceManager.isActiveTile).thenReturn(false)
+            customTileStatePersister.persistState(
+                TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+                TEST_TILE,
+            )
+            val tiles = collectValues(underTest.tiles)
+
+            val initJob = backgroundScope.launch { underTest.init() }
+            advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+
+            // Is still suspended
+            assertThat(initJob.isActive).isTrue()
+            assertThat(tiles()).isEmpty()
+        }
+
+    private companion object {
+
+        val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+        val TEST_TILE_SPEC = TileSpec.create(TEST_COMPONENT)
+        val TEST_USER = UserHandle.of(1)!!
+        val TEST_TILE =
+            Tile().apply {
+                label = "test_tile_1"
+                icon = Icon.createWithContentUri("file://test_1")
+            }
+        val TEST_DEFAULTS = CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index fab290d..7b2ac90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.tiles.impl.flashlight.domain
 
-import android.graphics.drawable.Drawable
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -26,7 +25,6 @@
 import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
-import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
 import org.junit.Test
@@ -37,7 +35,7 @@
 class FlashlightMapperTest : SysuiTestCase() {
     private val kosmos = Kosmos()
     private val qsTileConfig = kosmos.qsFlashlightTileConfig
-    private val mapper by lazy { FlashlightMapper(context) }
+    private val mapper by lazy { FlashlightMapper(context.orCreateTestableResources.resources) }
 
     @Test
     fun mapsDisabledDataToInactiveState() {
@@ -58,12 +56,7 @@
 
     @Test
     fun mapsEnabledDataToOnIconState() {
-        val fakeDrawable = mock<Drawable>()
-        context.orCreateTestableResources.addOverride(
-            R.drawable.qs_flashlight_icon_on,
-            fakeDrawable
-        )
-        val expectedIcon = Icon.Loaded(fakeDrawable, null)
+        val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_on, null)
 
         val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
 
@@ -73,12 +66,7 @@
 
     @Test
     fun mapsDisabledDataToOffIconState() {
-        val fakeDrawable = mock<Drawable>()
-        context.orCreateTestableResources.addOverride(
-            R.drawable.qs_flashlight_icon_off,
-            fakeDrawable
-        )
-        val expectedIcon = Icon.Loaded(fakeDrawable, null)
+        val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_off, null)
 
         val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
index 820c056..8791877 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.tiles.impl.location.domain
 
-import android.graphics.drawable.Drawable
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -26,7 +25,6 @@
 import com.android.systemui.qs.tiles.impl.location.qsLocationTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
-import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth
 import junit.framework.Assert
 import org.junit.Test
@@ -37,7 +35,8 @@
 class LocationTileMapperTest : SysuiTestCase() {
     private val kosmos = Kosmos()
     private val qsTileConfig = kosmos.qsLocationTileConfig
-    private val mapper by lazy { LocationTileMapper(context) }
+
+    private val mapper by lazy { LocationTileMapper(context.orCreateTestableResources.resources) }
 
     @Test
     fun mapsDisabledDataToInactiveState() {
@@ -57,9 +56,7 @@
 
     @Test
     fun mapsEnabledDataToOnIconState() {
-        val fakeDrawable = mock<Drawable>()
-        context.orCreateTestableResources.addOverride(R.drawable.qs_location_icon_on, fakeDrawable)
-        val expectedIcon = Icon.Loaded(fakeDrawable, null)
+        val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_on, null)
 
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))
 
@@ -69,9 +66,7 @@
 
     @Test
     fun mapsDisabledDataToOffIconState() {
-        val fakeDrawable = mock<Drawable>()
-        context.orCreateTestableResources.addOverride(R.drawable.qs_location_icon_off, fakeDrawable)
-        val expectedIcon = Icon.Loaded(fakeDrawable, null)
+        val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_off, null)
 
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index c3294ff..18b7168 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
@@ -61,7 +62,6 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
-import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -273,6 +273,9 @@
     }
 
     @Test
+    fun startsInLockscreenScene() = testScope.runTest { assertCurrentScene(SceneKey.Lockscreen) }
+
+    @Test
     fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
         testScope.runTest {
             emulateUserDrivenTransition(SceneKey.Bouncer)
@@ -336,7 +339,7 @@
         testScope.runTest {
             val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
-            assertTrue(deviceEntryInteractor.canSwipeToEnter.value)
+            assertThat(deviceEntryInteractor.canSwipeToEnter.value).isTrue()
             assertCurrentScene(SceneKey.Lockscreen)
 
             // Emulate a user swipe to dismiss the lockscreen.
@@ -775,11 +778,11 @@
     private suspend fun TestScope.dismissIme(
         showImeBeforeDismissing: Boolean = true,
     ) {
-        bouncerViewModel.authMethodViewModel.value?.apply {
+        (bouncerViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
             if (showImeBeforeDismissing) {
-                onImeVisibilityChanged(true)
+                it.onImeVisibilityChanged(true)
             }
-            onImeVisibilityChanged(false)
+            it.onImeVisibilityChanged(false)
             runCurrent()
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index c4ec56c..3cb97e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -145,6 +145,18 @@
         }
 
     @Test
+    fun startsInLockscreenScene() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState()
+
+            underTest.start()
+            runCurrent()
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
     fun switchToLockscreenWhenDeviceLocks() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
@@ -467,7 +479,7 @@
             underTest.start()
             runCurrent()
 
-            bouncerInteractor.onImeHidden()
+            bouncerInteractor.onImeHiddenByUser()
             runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 9c08f5e..355e75d 100644
--- a/packages/SystemUI/res/drawable/notification_material_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -18,7 +18,7 @@
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
         android:color="?android:attr/colorControlHighlight">
-    <item android:id="@+id/notification_background_color_layer">
+    <item>
         <shape>
             <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
         </shape>
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index af29cad..50241cd 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -111,107 +111,57 @@
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/bluetooth_toggle"
-                app:layout_constraintBottom_toTopOf="@+id/see_all_text" />
+                app:layout_constraintBottom_toTopOf="@+id/see_all_button" />
 
-            <androidx.constraintlayout.widget.Group
-                android:id="@+id/see_all_layout_group"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:visibility="gone"
-                app:constraint_referenced_ids="ic_arrow,see_all_text" />
-
-            <View
-                android:id="@+id/see_all_clickable_row"
+            <Button
+                android:id="@+id/see_all_button"
+                style="@style/BluetoothTileDialog.Device"
+                android:paddingEnd="0dp"
+                android:paddingStart="20dp"
+                android:background="@drawable/bluetooth_tile_dialog_bg_off"
                 android:layout_width="0dp"
-                android:layout_height="0dp"
+                android:layout_height="64dp"
                 android:contentDescription="@string/accessibility_bluetooth_device_settings_see_all"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/device_list"
-                app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" />
-
-            <ImageView
-                android:id="@+id/ic_arrow"
-                android:layout_marginStart="36dp"
-                android:layout_width="24dp"
-                android:layout_height="24dp"
-                android:importantForAccessibility="no"
-                android:gravity="center_vertical"
-                android:src="@drawable/ic_arrow_forward"
-                app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintEnd_toStartOf="@id/see_all_text"
-                app:layout_constraintTop_toBottomOf="@id/device_list" />
-
-            <TextView
-                android:id="@+id/see_all_text"
-                style="@style/BluetoothTileDialog.Device"
-                android:layout_width="0dp"
-                android:layout_height="64dp"
-                android:maxLines="1"
-                android:ellipsize="end"
-                android:gravity="center_vertical"
-                android:importantForAccessibility="no"
-                android:clickable="false"
-                android:layout_marginStart="0dp"
-                android:paddingStart="20dp"
+                app:layout_constraintBottom_toTopOf="@+id/pair_new_device_button"
+                android:drawableStart="@drawable/ic_arrow_forward"
+                android:drawablePadding="20dp"
+                android:drawableTint="?android:attr/textColorPrimary"
                 android:text="@string/see_all_bluetooth_devices"
                 android:textSize="14sp"
                 android:textAppearance="@style/TextAppearance.Dialog.Title"
-                app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text"
-                app:layout_constraintStart_toEndOf="@+id/ic_arrow"
-                app:layout_constraintTop_toBottomOf="@id/device_list"
-                app:layout_constraintEnd_toEndOf="parent" />
+                android:textDirection="locale"
+                android:textAlignment="viewStart"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:visibility="gone" />
 
-            <androidx.constraintlayout.widget.Group
-                android:id="@+id/pair_new_device_layout_group"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:visibility="gone"
-                app:constraint_referenced_ids="ic_add,pair_new_device_text" />
-
-            <View
-                android:id="@+id/pair_new_device_clickable_row"
+            <Button
+                android:id="@+id/pair_new_device_button"
+                style="@style/BluetoothTileDialog.Device"
+                android:paddingEnd="0dp"
+                android:paddingStart="20dp"
+                android:background="@drawable/bluetooth_tile_dialog_bg_off"
                 android:layout_width="0dp"
-                android:layout_height="0dp"
+                android:layout_height="64dp"
                 android:contentDescription="@string/accessibility_bluetooth_device_settings_pair_new_device"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/see_all_text"
-                app:layout_constraintBottom_toTopOf="@+id/done_button" />
-
-            <ImageView
-                android:id="@+id/ic_add"
-                android:layout_width="24dp"
-                android:layout_height="24dp"
-                android:layout_marginStart="36dp"
-                android:gravity="center_vertical"
-                android:importantForAccessibility="no"
-                android:src="@drawable/ic_add"
-                app:layout_constraintBottom_toTopOf="@id/done_button"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintEnd_toStartOf="@id/pair_new_device_text"
-                app:layout_constraintTop_toBottomOf="@id/see_all_text"
-                android:tint="?android:attr/textColorPrimary" />
-
-            <TextView
-                android:id="@+id/pair_new_device_text"
-                style="@style/BluetoothTileDialog.Device"
-                android:layout_width="0dp"
-                android:layout_height="64dp"
-                android:maxLines="1"
-                android:ellipsize="end"
-                android:gravity="center_vertical"
-                android:importantForAccessibility="no"
-                android:clickable="false"
-                android:layout_marginStart="0dp"
-                android:paddingStart="20dp"
+                app:layout_constraintTop_toBottomOf="@+id/see_all_button"
+                app:layout_constraintBottom_toTopOf="@+id/done_button"
+                android:drawableStart="@drawable/ic_add"
+                android:drawablePadding="20dp"
+                android:drawableTint="?android:attr/textColorPrimary"
                 android:text="@string/pair_new_bluetooth_devices"
                 android:textSize="14sp"
                 android:textAppearance="@style/TextAppearance.Dialog.Title"
-                app:layout_constraintStart_toEndOf="@+id/ic_add"
-                app:layout_constraintTop_toBottomOf="@id/see_all_text"
-                app:layout_constraintEnd_toEndOf="parent" />
+                android:textDirection="locale"
+                android:textAlignment="viewStart"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:visibility="gone" />
 
             <Button
                 android:id="@+id/done_button"
@@ -227,7 +177,7 @@
                 android:maxLines="1"
                 android:text="@string/inline_done_button"
                 app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintTop_toBottomOf="@id/pair_new_device_text"
+                app:layout_constraintTop_toBottomOf="@id/pair_new_device_button"
                 app:layout_constraintBottom_toBottomOf="parent" />
         </androidx.constraintlayout.widget.ConstraintLayout>
     </androidx.core.widget.NestedScrollView>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index b704f3c..1edb551 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -71,7 +71,7 @@
     private final DisplayTracker mDisplayTracker;
     private final AccessibilityLogger mA11yLogger;
 
-    private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl;
+    private MagnificationConnectionImpl mMagnificationConnectionImpl;
     private SysUiState mSysUiState;
 
     @VisibleForTesting
@@ -220,7 +220,7 @@
     }
 
     @MainThread
-    void setScale(int displayId, float scale) {
+    void setScaleForWindowMagnification(int displayId, float scale) {
         final WindowMagnificationController windowMagnificationController =
                 mMagnificationControllerSupplier.get(displayId);
         if (windowMagnificationController != null) {
@@ -321,37 +321,37 @@
     final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
         @Override
         public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) {
-            if (mWindowMagnificationConnectionImpl != null) {
-                mWindowMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame);
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame);
             }
         }
 
         @Override
         public void onSourceBoundsChanged(int displayId, Rect sourceBounds) {
-            if (mWindowMagnificationConnectionImpl != null) {
-                mWindowMagnificationConnectionImpl.onSourceBoundsChanged(displayId, sourceBounds);
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onSourceBoundsChanged(displayId, sourceBounds);
             }
         }
 
         @Override
         public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
-            if (mWindowMagnificationConnectionImpl != null) {
-                mWindowMagnificationConnectionImpl.onPerformScaleAction(
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onPerformScaleAction(
                         displayId, scale, updatePersistence);
             }
         }
 
         @Override
         public void onAccessibilityActionPerformed(int displayId) {
-            if (mWindowMagnificationConnectionImpl != null) {
-                mWindowMagnificationConnectionImpl.onAccessibilityActionPerformed(displayId);
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onAccessibilityActionPerformed(displayId);
             }
         }
 
         @Override
         public void onMove(int displayId) {
-            if (mWindowMagnificationConnectionImpl != null) {
-                mWindowMagnificationConnectionImpl.onMove(displayId);
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onMove(displayId);
             }
         }
 
@@ -394,8 +394,8 @@
                 @Override
                 public void onMagnifierScale(int displayId, float scale,
                         boolean updatePersistence) {
-                    if (mWindowMagnificationConnectionImpl != null) {
-                        mWindowMagnificationConnectionImpl.onPerformScaleAction(
+                    if (mMagnificationConnectionImpl != null) {
+                        mMagnificationConnectionImpl.onPerformScaleAction(
                                 displayId, scale, updatePersistence);
                     }
                     mA11yLogger.logThrottled(
@@ -454,8 +454,8 @@
             if (magnificationSettingsController != null) {
                 magnificationSettingsController.closeMagnificationSettings();
             }
-            if (mWindowMagnificationConnectionImpl != null) {
-                mWindowMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode);
+            if (mMagnificationConnectionImpl != null) {
+                mMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode);
             }
         }
     }
@@ -500,12 +500,12 @@
     }
 
     private void setWindowMagnificationConnection() {
-        if (mWindowMagnificationConnectionImpl == null) {
-            mWindowMagnificationConnectionImpl = new WindowMagnificationConnectionImpl(this,
+        if (mMagnificationConnectionImpl == null) {
+            mMagnificationConnectionImpl = new MagnificationConnectionImpl(this,
                     mHandler);
         }
         mAccessibilityManager.setWindowMagnificationConnection(
-                mWindowMagnificationConnectionImpl);
+                mMagnificationConnectionImpl);
     }
 
     private void clearWindowMagnificationConnection() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
rename to packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
index 5666851..5f0d496 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
@@ -32,7 +32,7 @@
  *
  * @see IWindowMagnificationConnection
  */
-class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.Stub {
+class MagnificationConnectionImpl extends IWindowMagnificationConnection.Stub {
 
     private static final String TAG = "WindowMagnificationConnectionImpl";
 
@@ -40,7 +40,7 @@
     private final Magnification mMagnification;
     private final Handler mHandler;
 
-    WindowMagnificationConnectionImpl(@NonNull Magnification magnification,
+    MagnificationConnectionImpl(@NonNull Magnification magnification,
             @Main Handler mainHandler) {
         mMagnification = magnification;
         mHandler = mainHandler;
@@ -57,8 +57,8 @@
     }
 
     @Override
-    public void setScale(int displayId, float scale) {
-        mHandler.post(() -> mMagnification.setScale(displayId, scale));
+    public void setScaleForWindowMagnification(int displayId, float scale) {
+        mHandler.post(() -> mMagnification.setScaleForWindowMagnification(displayId, scale));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 1ac4163..ab23564 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -30,6 +30,7 @@
 import android.graphics.PixelFormat;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -168,7 +169,7 @@
     // HAT received from LockSettingsService when credential is verified.
     @Nullable private byte[] mCredentialAttestation;
 
-    // TODO(b/287311775): remove when legacy prompt is replaced
+    // TODO(b/313469218): remove when legacy prompt is replaced
     @Deprecated
     static class Config {
         Context mContext;
@@ -220,6 +221,9 @@
             mHandler.postDelayed(() -> {
                 addCredentialView(false /* animatePanel */, true /* animateContents */);
             }, mConfig.mSkipAnimation ? 0 : ANIMATE_CREDENTIAL_START_DELAY_MS);
+
+            // TODO(b/313469218): Remove Config
+            mConfig.mPromptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index 11a5d8b..3defec5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -485,8 +485,7 @@
     ): Int =
         if (isPendingConfirmation) {
             when (sensorType) {
-                FingerprintSensorType.POWER_BUTTON ->
-                    R.string.security_settings_sfps_enroll_find_sensor_message
+                FingerprintSensorType.POWER_BUTTON -> -1
                 else -> R.string.fingerprint_dialog_authenticated_confirmation
             }
         } else if (isAuthenticating || isAuthenticated) {
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 b598631..7c46339 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
@@ -105,9 +105,9 @@
     val isUserSwitcherVisible: Boolean
         get() = repository.isUserSwitcherVisible
 
-    private val _onImeHidden = MutableSharedFlow<Unit>()
-    /** Provide the onImeHidden events from the bouncer */
-    val onImeHidden: SharedFlow<Unit> = _onImeHidden
+    private val _onImeHiddenByUser = MutableSharedFlow<Unit>()
+    /** Emits a [Unit] each time the IME (keyboard) is hidden by the user. */
+    val onImeHiddenByUser: SharedFlow<Unit> = _onImeHiddenByUser
 
     init {
         if (flags.isEnabled()) {
@@ -230,9 +230,9 @@
         repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod()))
     }
 
-    /** Notifies the interactor that the input method editor has been hidden. */
-    suspend fun onImeHidden() {
-        _onImeHidden.emit(Unit)
+    /** Notifies that the input method editor (software keyboard) has been hidden by the user. */
+    suspend fun onImeHiddenByUser() {
+        _onImeHiddenByUser.emit(Unit)
     }
 
     private fun promptMessage(authMethod: AuthenticationMethodModel): String {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 8024874..e379dab 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -46,9 +46,6 @@
      */
     val animateFailure: StateFlow<Boolean> = _animateFailure.asStateFlow()
 
-    /** Whether the input method editor (for example, the software keyboard) is visible. */
-    private var isImeVisible: Boolean = false
-
     /** The authentication method that corresponds to this view model. */
     abstract val authenticationMethod: AuthenticationMethodModel
 
@@ -68,7 +65,7 @@
     /**
      * Notifies that the UI has been hidden from the user (after any transitions have completed).
      */
-    fun onHidden() {
+    open fun onHidden() {
         clearInput()
         interactor.resetMessage()
     }
@@ -79,18 +76,6 @@
     }
 
     /**
-     * Notifies that the input method editor (for example, the software keyboard) has been shown or
-     * hidden.
-     */
-    suspend fun onImeVisibilityChanged(isVisible: Boolean) {
-        if (isImeVisible && !isVisible) {
-            interactor.onImeHidden()
-        }
-
-        isImeVisible = isVisible
-    }
-
-    /**
      * Notifies that the failure animation has been shown. This should be called to consume a `true`
      * value in [animateFailure].
      */
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 a15698e..45d18128 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
@@ -21,8 +21,11 @@
 import com.android.systemui.res.R
 import kotlinx.coroutines.CoroutineScope
 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.stateIn
 
 /** Holds UI state and handles user input for the password bouncer UI. */
 class PasswordBouncerViewModel(
@@ -45,6 +48,32 @@
 
     override val throttlingMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
 
+    /** Whether the input method editor (for example, the software keyboard) is visible. */
+    private var isImeVisible: Boolean = false
+
+    /** Whether the text field element currently has focus. */
+    private val isTextFieldFocused = MutableStateFlow(false)
+
+    /** Whether the UI should request focus on the text field element. */
+    val isTextFieldFocusRequested =
+        combine(
+                interactor.isThrottled,
+                isTextFieldFocused,
+            ) { isThrottled, hasFocus ->
+                !isThrottled && !hasFocus
+            }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = !interactor.isThrottled.value && !isTextFieldFocused.value,
+            )
+
+    override fun onHidden() {
+        super.onHidden()
+        isImeVisible = false
+        isTextFieldFocused.value = false
+    }
+
     override fun clearInput() {
         _password.value = ""
     }
@@ -72,4 +101,21 @@
             tryAuthenticate()
         }
     }
+
+    /**
+     * Notifies that the input method editor (for example, the software keyboard) has been shown or
+     * hidden.
+     */
+    suspend fun onImeVisibilityChanged(isVisible: Boolean) {
+        if (isImeVisible && !isVisible && !interactor.isThrottled.value) {
+            interactor.onImeHiddenByUser()
+        }
+
+        isImeVisible = isVisible
+    }
+
+    /** Notifies that the password text field has gained or lost focus. */
+    fun onTextFieldFocusChanged(isFocused: Boolean) {
+        isTextFieldFocused.value = isFocused
+    }
 }
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 715fb17..4cddb9c 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
@@ -103,8 +103,11 @@
                 initialValue = false,
             )
 
-    // Authenticated by a TrustAgent like trusted device, location, etc or by face auth.
-    private val passivelyAuthenticated =
+    /**
+     * Whether the user is currently authenticated by a TrustAgent like trusted device, location,
+     * etc., or by face auth.
+     */
+    private val isPassivelyAuthenticated =
         merge(
                 trustRepository.isCurrentUserTrusted,
                 deviceEntryFaceAuthRepository.isAuthenticated,
@@ -117,25 +120,31 @@
      * mechanism like face or trust manager. This returns `false` whenever the lockscreen has been
      * dismissed.
      *
+     * A value of `null` is meaningless and is used as placeholder while the actual value is still
+     * being loaded in the background.
+     *
      * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
      * UI.
      */
-    val canSwipeToEnter =
+    val canSwipeToEnter: StateFlow<Boolean?> =
         combine(
                 // This is true when the user has chosen to show the lockscreen but has not made it
                 // secure.
                 authenticationInteractor.authenticationMethod.map {
                     it == AuthenticationMethodModel.None && repository.isLockscreenEnabled()
                 },
-                passivelyAuthenticated,
+                isPassivelyAuthenticated,
                 isDeviceEntered
-            ) { isSwipeAuthMethod, passivelyAuthenticated, isDeviceEntered ->
-                (isSwipeAuthMethod || passivelyAuthenticated) && !isDeviceEntered
+            ) { isSwipeAuthMethod, isPassivelyAuthenticated, isDeviceEntered ->
+                (isSwipeAuthMethod || isPassivelyAuthenticated) && !isDeviceEntered
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = false,
+                // Starts as null to prevent downstream collectors from falsely assuming that the
+                // user can or cannot swipe to enter the device while the real value is being loaded
+                // from upstream data sources.
+                initialValue = null,
             )
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 2a0d6a8..0c24752 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -545,28 +545,12 @@
             unreleasedFlag("clipboard_shared_transitions", teamfood = true)
 
     /**
-     * Whether the scene container (Flexiglass) is enabled. Note that [SCENE_CONTAINER] should be
-     * checked and toggled together with [SCENE_CONTAINER_ENABLED] so that ProGuard can remove
-     * unused code from our APK at compile time.
+     * Whether the scene container (Flexiglass) is enabled. Note that SceneContainerFlags#isEnabled
+     * should be checked and toggled together with [SCENE_CONTAINER_ENABLED] so that ProGuard can
+     * remove unused code from our APK at compile time.
      */
     // TODO(b/283300105): Tracking Bug
     @JvmField val SCENE_CONTAINER_ENABLED = false
-    @Deprecated(
-        message = """
-            Do not use this flag directly. Please use
-            [com.android.systemui.scene.shared.flag.SceneContainerFlags#isEnabled].
-
-            (Not really deprecated but using this as a simple way to bring attention to the above).
-        """,
-        replaceWith = ReplaceWith(
-            "com.android.systemui.scene.shared.flag.SceneContainerFlags#isEnabled",
-        ),
-        level = DeprecationLevel.WARNING,
-    )
-    @JvmField val SCENE_CONTAINER = resourceBooleanFlag(
-        R.bool.config_sceneContainerFrameworkEnabled,
-        "scene_container",
-    )
 
     // 1900
     @JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 6f84d13..59c798b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -287,6 +287,10 @@
             return
         }
 
+        if (smartSpaceView != null) {
+            parentView.removeView(smartSpaceView)
+        }
+
         smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
 
         val topPadding: Int =
@@ -363,12 +367,13 @@
             ),
         )
 
+        setUpUdfps(previewContext, rootView)
+
         disposables.add(
             PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
                 if (keyguardBottomAreaRefactor()) {
                     setupShortcuts(keyguardRootView)
                 }
-                setUpUdfps(previewContext, rootView)
 
                 if (!shouldHideClock) {
                     setUpClock(previewContext, rootView)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 11db69b..6c930b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -255,6 +255,10 @@
             for (QSTile tile : tiles) {
                 addTile(tile, collapsedView);
             }
+        } else {
+            for (QSPanelControllerBase.TileRecord record : mRecords) {
+                record.tile.addCallback(record.callback);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
index a321eef..6f5dea3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
@@ -18,17 +18,19 @@
 
 import android.content.ComponentName
 import android.content.Context
+import android.content.SharedPreferences
 import android.service.quicksettings.Tile
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
+import javax.inject.Inject
 import org.json.JSONException
 import org.json.JSONObject
-import javax.inject.Inject
 
 data class TileServiceKey(val componentName: ComponentName, val user: Int) {
     private val string = "${componentName.flattenToString()}:$user"
     override fun toString() = string
 }
+
 private const val STATE = "state"
 private const val LABEL = "label"
 private const val SUBTITLE = "subtitle"
@@ -44,12 +46,7 @@
  * It persists the state from a [Tile] necessary to present the view in the same state when
  * retrieved, with the exception of the icon.
  */
-class CustomTileStatePersister @Inject constructor(context: Context) {
-    companion object {
-        private const val FILE_NAME = "custom_tiles_state"
-    }
-
-    private val sharedPreferences = context.getSharedPreferences(FILE_NAME, 0)
+interface CustomTileStatePersister {
 
     /**
      * Read the state from [SharedPreferences].
@@ -58,7 +55,31 @@
      *
      * Any fields that have not been saved will be set to `null`
      */
-    fun readState(key: TileServiceKey): Tile? {
+    fun readState(key: TileServiceKey): Tile?
+    /**
+     * Persists the state into [SharedPreferences].
+     *
+     * The implementation does not store fields that are `null` or icons.
+     */
+    fun persistState(key: TileServiceKey, tile: Tile)
+    /**
+     * Removes the state for a given tile, user pair.
+     *
+     * Used when the tile is removed by the user.
+     */
+    fun removeState(key: TileServiceKey)
+}
+
+// TODO(b/299909989) Merge this class into into CustomTileRepository
+class CustomTileStatePersisterImpl @Inject constructor(context: Context) :
+    CustomTileStatePersister {
+    companion object {
+        private const val FILE_NAME = "custom_tiles_state"
+    }
+
+    private val sharedPreferences: SharedPreferences = context.getSharedPreferences(FILE_NAME, 0)
+
+    override fun readState(key: TileServiceKey): Tile? {
         val state = sharedPreferences.getString(key.toString(), null) ?: return null
         return try {
             readTileFromString(state)
@@ -68,23 +89,13 @@
         }
     }
 
-    /**
-     * Persists the state into [SharedPreferences].
-     *
-     * The implementation does not store fields that are `null` or icons.
-     */
-    fun persistState(key: TileServiceKey, tile: Tile) {
+    override fun persistState(key: TileServiceKey, tile: Tile) {
         val state = writeToString(tile)
 
         sharedPreferences.edit().putString(key.toString(), state).apply()
     }
 
-    /**
-     * Removes the state for a given tile, user pair.
-     *
-     * Used when the tile is removed by the user.
-     */
-    fun removeState(key: TileServiceKey) {
+    override fun removeState(key: TileServiceKey) {
         sharedPreferences.edit().remove(key.toString()).apply()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 69fe46a..529d684 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -109,7 +109,7 @@
     // Only read and modified in main thread (where click events come through).
     private int mClickEventId = 0;
 
-    private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+    private final ArraySet<Callback> mCallbacks = new ArraySet<>();
     private final Object mStaleListener = new Object();
     protected TState mState;
     private TState mTmpState;
@@ -444,9 +444,9 @@
     }
 
     private void handleStateChanged() {
-        if (mCallbacks.size() != 0) {
+        if (!mCallbacks.isEmpty()) {
             for (int i = 0; i < mCallbacks.size(); i++) {
-                mCallbacks.get(i).onStateChanged(mState);
+                mCallbacks.valueAt(i).onStateChanged(mState);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
index 94137c8..4a34276 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles.di
 
+import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.CustomTileStatePersisterImpl
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerImpl
 import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
@@ -52,4 +54,7 @@
     fun bindQSTileIntentUserInputHandler(
         impl: QSTileIntentUserInputHandlerImpl
     ): QSTileIntentUserInputHandler
+
+    @Binds
+    fun bindCustomTileStatePersister(impl: CustomTileStatePersisterImpl): CustomTileStatePersister
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
index 5bdb592..db3cf0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
@@ -81,10 +81,8 @@
     private lateinit var toggleView: Switch
     private lateinit var subtitleTextView: TextView
     private lateinit var doneButton: View
-    private lateinit var seeAllViewGroup: View
-    private lateinit var pairNewDeviceViewGroup: View
-    private lateinit var seeAllRow: View
-    private lateinit var pairNewDeviceRow: View
+    private lateinit var seeAllButton: View
+    private lateinit var pairNewDeviceButton: View
     private lateinit var deviceListView: RecyclerView
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -99,10 +97,8 @@
         toggleView = requireViewById(R.id.bluetooth_toggle)
         subtitleTextView = requireViewById(R.id.bluetooth_tile_dialog_subtitle) as TextView
         doneButton = requireViewById(R.id.done_button)
-        seeAllViewGroup = requireViewById(R.id.see_all_layout_group)
-        pairNewDeviceViewGroup = requireViewById(R.id.pair_new_device_layout_group)
-        seeAllRow = requireViewById(R.id.see_all_clickable_row)
-        pairNewDeviceRow = requireViewById(R.id.pair_new_device_clickable_row)
+        seeAllButton = requireViewById(R.id.see_all_button)
+        pairNewDeviceButton = requireViewById(R.id.pair_new_device_button)
         deviceListView = requireViewById<RecyclerView>(R.id.device_list)
 
         setupToggle()
@@ -110,8 +106,8 @@
 
         subtitleTextView.text = context.getString(subtitleResIdInitialValue)
         doneButton.setOnClickListener { dismiss() }
-        seeAllRow.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
-        pairNewDeviceRow.setOnClickListener {
+        seeAllButton.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
+        pairNewDeviceButton.setOnClickListener {
             bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
         }
     }
@@ -134,8 +130,8 @@
             }
             if (isActive) {
                 deviceItemAdapter.refreshDeviceItemList(deviceItem) {
-                    seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE
-                    pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE
+                    seeAllButton.visibility = if (showSeeAll) VISIBLE else GONE
+                    pairNewDeviceButton.visibility = if (showPairNewDevice) VISIBLE else GONE
                     lastUiUpdateMs = systemClock.elapsedRealtime()
                     lastItemRow = itemRow
                     logger.logDeviceUiUpdate(lastUiUpdateMs - start)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
index 34c2aba..5d5e747 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
@@ -20,7 +20,6 @@
 import android.content.Intent
 import android.os.Bundle
 import android.view.View
-import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.DialogCuj
@@ -40,6 +39,8 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.produce
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -63,26 +64,25 @@
 
     private var job: Job? = null
 
-    @VisibleForTesting internal var dialog: BluetoothTileDialog? = null
-
     /**
      * Shows the dialog.
      *
      * @param context The context in which the dialog is displayed.
      * @param view The view from which the dialog is shown.
      */
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
     fun showDialog(context: Context, view: View?) {
-        dismissDialog()
-
-        var updateDeviceItemJob: Job? = null
-        var updateDialogUiJob: Job? = null
+        cancelJob()
 
         job =
             coroutineScope.launch(mainDispatcher) {
-                dialog = createBluetoothTileDialog(context)
+                var updateDeviceItemJob: Job?
+                var updateDialogUiJob: Job? = null
+                val dialog = createBluetoothTileDialog(context)
+
                 view?.let {
                     dialogLaunchAnimator.showFromView(
-                        dialog!!,
+                        dialog,
                         it,
                         animateBackgroundBoundsChange = true,
                         cuj =
@@ -92,9 +92,8 @@
                             )
                     )
                 }
-                    ?: dialog!!.show()
+                    ?: dialog.show()
 
-                updateDeviceItemJob?.cancel()
                 updateDeviceItemJob = launch {
                     deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD)
                 }
@@ -102,7 +101,7 @@
                 bluetoothStateInteractor.bluetoothStateUpdate
                     .filterNotNull()
                     .onEach {
-                        dialog!!.onBluetoothStateUpdated(it, getSubtitleResId(it))
+                        dialog.onBluetoothStateUpdated(it, getSubtitleResId(it))
                         updateDeviceItemJob?.cancel()
                         updateDeviceItemJob = launch {
                             deviceItemInteractor.updateDeviceItems(
@@ -129,7 +128,7 @@
                     .onEach {
                         updateDialogUiJob?.cancel()
                         updateDialogUiJob = launch {
-                            dialog?.onDeviceItemUpdated(
+                            dialog.onDeviceItemUpdated(
                                 it.take(MAX_DEVICE_ITEM_ENTRY),
                                 showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
                                 showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
@@ -138,15 +137,15 @@
                     }
                     .launchIn(this)
 
-                dialog!!
-                    .bluetoothStateToggle
+                dialog.bluetoothStateToggle
                     .onEach { bluetoothStateInteractor.isBluetoothEnabled = it }
                     .launchIn(this)
 
-                dialog!!
-                    .deviceItemClick
+                dialog.deviceItemClick
                     .onEach { deviceItemInteractor.updateDeviceItemOnClick(it) }
                     .launchIn(this)
+
+                produce<Unit> { awaitClose { dialog.cancel() } }
             }
     }
 
@@ -161,7 +160,7 @@
                 logger,
                 context
             )
-            .apply { SystemUIDialog.registerDismissListener(this) { dismissDialog() } }
+            .apply { SystemUIDialog.registerDismissListener(this) { cancelJob() } }
     }
 
     override fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View) {
@@ -188,15 +187,13 @@
         startSettingsActivity(Intent(ACTION_PAIR_NEW_DEVICE), view)
     }
 
-    private fun dismissDialog() {
+    private fun cancelJob() {
         job?.cancel()
         job = null
-        dialog?.dismiss()
-        dialog = null
     }
 
     private fun startSettingsActivity(intent: Intent, view: View) {
-        dialog?.run {
+        if (job?.isActive == true) {
             intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
             activityStarter.postStartActivityDismissingKeyguard(
                 intent,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
index 76fbf8e..fcd45a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
@@ -168,26 +168,30 @@
         )
     }
 
-    internal fun updateDeviceItemOnClick(deviceItem: DeviceItem) {
-        logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type)
+    internal suspend fun updateDeviceItemOnClick(deviceItem: DeviceItem) {
+        withContext(backgroundDispatcher) {
+            logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type)
 
-        deviceItem.cachedBluetoothDevice.apply {
-            when (deviceItem.type) {
-                DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> {
-                    disconnect()
-                    uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
-                }
-                DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
-                    setActive()
-                    uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
-                }
-                DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {
-                    disconnect()
-                    uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT)
-                }
-                DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
-                    connect()
-                    uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
+            deviceItem.cachedBluetoothDevice.apply {
+                when (deviceItem.type) {
+                    DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> {
+                        disconnect()
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
+                    }
+                    DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
+                        setActive()
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
+                    }
+                    DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {
+                        disconnect()
+                        uiEventLogger.log(
+                            BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT
+                        )
+                    }
+                    DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
+                        connect()
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index c390695b..cfb5442 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.tiles.impl.airplane.domain
 
-import android.content.Context
+import android.content.res.Resources
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
@@ -26,29 +27,27 @@
 import javax.inject.Inject
 
 /** Maps [AirplaneModeTileModel] to [QSTileState]. */
-class AirplaneModeMapper @Inject constructor(private val context: Context) :
+class AirplaneModeMapper @Inject constructor(@Main private val resources: Resources) :
     QSTileDataToStateMapper<AirplaneModeTileModel> {
 
     override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
-        QSTileState.build(context, config.uiConfig) {
+        QSTileState.build(resources, config.uiConfig) {
             val icon =
-                Icon.Loaded(
-                    context.getDrawable(
-                        if (data.isEnabled) {
-                            R.drawable.qs_airplane_icon_on
-                        } else {
-                            R.drawable.qs_airplane_icon_off
-                        }
-                    )!!,
+                Icon.Resource(
+                    if (data.isEnabled) {
+                        R.drawable.qs_airplane_icon_on
+                    } else {
+                        R.drawable.qs_airplane_icon_off
+                    },
                     contentDescription = null
                 )
             this.icon = { icon }
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
-                secondaryLabel = context.resources.getStringArray(R.array.tile_states_airplane)[2]
+                secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2]
             } else {
                 activationState = QSTileState.ActivationState.INACTIVE
-                secondaryLabel = context.resources.getStringArray(R.array.tile_states_airplane)[1]
+                secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[1]
             }
             contentDescription = label
             supportedActions =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt
new file mode 100644
index 0000000..869f6f32
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/commons/TileExt.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.commons
+
+import android.service.quicksettings.Tile
+
+fun Tile.copy(): Tile =
+    Tile().also {
+        it.icon = icon
+        it.label = label
+        it.subtitle = subtitle
+        it.contentDescription = contentDescription
+        it.stateDescription = stateDescription
+        it.activityLaunchForClick = activityLaunchForClick
+        it.state = state
+    }
+
+fun Tile.setFrom(otherTile: Tile) {
+    if (otherTile.icon != null) {
+        icon = otherTile.icon
+    }
+    if (otherTile.customLabel != null) {
+        label = otherTile.customLabel
+    }
+    if (otherTile.subtitle != null) {
+        subtitle = otherTile.subtitle
+    }
+    if (otherTile.contentDescription != null) {
+        contentDescription = otherTile.contentDescription
+    }
+    if (otherTile.stateDescription != null) {
+        stateDescription = otherTile.stateDescription
+    }
+    activityLaunchForClick = otherTile.activityLaunchForClick
+    state = otherTile.state
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
new file mode 100644
index 0000000..ca5302e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.data.repository
+
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.commons.setFrom
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository store the [Tile] associated with the custom tile. It lives on [QSTileScope] which
+ * allows it to survive service rebinding. Given that, it provides the last received state when
+ * connected again.
+ */
+interface CustomTileRepository {
+
+    /**
+     * Restores the [Tile] if it's [isPersistable]. Restored [Tile] will be available via [getTile]
+     * (but there is no guarantee that restoration is synchronous) and emitted in [getTiles] for a
+     * corresponding [user].
+     */
+    suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean)
+
+    /** Returns [Tile] updates for a [user]. */
+    fun getTiles(user: UserHandle): Flow<Tile>
+
+    /**
+     * Return current [Tile] for a [user] or null if the [user] doesn't match currently cached one.
+     * Suspending until [getTiles] returns something is a way to wait for this to become available.
+     *
+     * @throws IllegalStateException when there is no current tile.
+     */
+    fun getTile(user: UserHandle): Tile?
+
+    /**
+     * Updates tile with the non-null values from [newTile]. Overwrites the current cache when
+     * [user] differs from the cached one. [isPersistable] tile will be persisted to be possibly
+     * loaded when the [restoreForTheUserIfNeeded].
+     */
+    suspend fun updateWithTile(
+        user: UserHandle,
+        newTile: Tile,
+        isPersistable: Boolean,
+    )
+
+    /**
+     * Updates tile with the values from [defaults]. Overwrites the current cache when [user]
+     * differs from the cached one. [isPersistable] tile will be persisted to be possibly loaded
+     * when the [restoreForTheUserIfNeeded].
+     */
+    suspend fun updateWithDefaults(
+        user: UserHandle,
+        defaults: CustomTileDefaults,
+        isPersistable: Boolean,
+    )
+}
+
+@QSTileScope
+class CustomTileRepositoryImpl
+@Inject
+constructor(
+    private val tileSpec: TileSpec.CustomTileSpec,
+    private val customTileStatePersister: CustomTileStatePersister,
+    @Background private val backgroundContext: CoroutineContext,
+) : CustomTileRepository {
+
+    private val tileUpdateMutex = Mutex()
+    private val tileWithUserState =
+        MutableSharedFlow<TileWithUser>(onBufferOverflow = BufferOverflow.DROP_OLDEST, replay = 1)
+
+    override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) {
+        if (isPersistable && getCurrentTileWithUser()?.user != user) {
+            withContext(backgroundContext) {
+                customTileStatePersister.readState(user.getKey())?.let {
+                    updateWithTile(
+                        user,
+                        it,
+                        true,
+                    )
+                }
+            }
+        }
+    }
+
+    override fun getTiles(user: UserHandle): Flow<Tile> =
+        tileWithUserState.filter { it.user == user }.map { it.tile }
+
+    override fun getTile(user: UserHandle): Tile? {
+        val tileWithUser =
+            getCurrentTileWithUser() ?: throw IllegalStateException("Tile is not set")
+        return if (tileWithUser.user == user) {
+            tileWithUser.tile
+        } else {
+            null
+        }
+    }
+
+    override suspend fun updateWithTile(
+        user: UserHandle,
+        newTile: Tile,
+        isPersistable: Boolean,
+    ) = updateTile(user, isPersistable) { setFrom(newTile) }
+
+    override suspend fun updateWithDefaults(
+        user: UserHandle,
+        defaults: CustomTileDefaults,
+        isPersistable: Boolean,
+    ) {
+        if (defaults is CustomTileDefaults.Result) {
+            updateTile(user, isPersistable) {
+                // Update the icon if it's not set or is the default icon.
+                val updateIcon = (icon == null || icon.isResourceEqual(defaults.icon))
+                if (updateIcon) {
+                    icon = defaults.icon
+                }
+                setDefaultLabel(defaults.label)
+            }
+        }
+    }
+
+    private suspend fun updateTile(
+        user: UserHandle,
+        isPersistable: Boolean,
+        update: Tile.() -> Unit
+    ): Unit =
+        tileUpdateMutex.withLock {
+            val currentTileWithUser = getCurrentTileWithUser()
+            val tileToUpdate =
+                if (currentTileWithUser?.user == user) {
+                    currentTileWithUser.tile.copy()
+                } else {
+                    Tile()
+                }
+            tileToUpdate.update()
+            if (isPersistable) {
+                withContext(backgroundContext) {
+                    customTileStatePersister.persistState(user.getKey(), tileToUpdate)
+                }
+            }
+            tileWithUserState.tryEmit(TileWithUser(user, tileToUpdate))
+        }
+
+    private fun getCurrentTileWithUser(): TileWithUser? = tileWithUserState.replayCache.lastOrNull()
+
+    /** Compare two icons, only works for resources. */
+    private fun Icon.isResourceEqual(icon2: Icon?): Boolean {
+        if (icon2 == null) {
+            return false
+        }
+        if (this === icon2) {
+            return true
+        }
+        if (type != Icon.TYPE_RESOURCE || icon2.type != Icon.TYPE_RESOURCE) {
+            return false
+        }
+        if (resId != icon2.resId) {
+            return false
+        }
+        return resPackage == icon2.resPackage
+    }
+
+    private fun UserHandle.getKey() = TileServiceKey(tileSpec.componentName, this.identifier)
+
+    private data class TileWithUser(val user: UserHandle, val tile: Tile)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
index 83767aa..d956fde 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepositoryImpl
 import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent
 import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import dagger.Binds
@@ -50,4 +52,6 @@
     fun bindCustomTileDefaultsRepository(
         impl: CustomTileDefaultsRepositoryImpl
     ): CustomTileDefaultsRepository
+
+    @Binds fun bindCustomTileRepository(impl: CustomTileRepositoryImpl): CustomTileRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
new file mode 100644
index 0000000..351bba5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.domain.interactor
+
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.external.TileServiceManager
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
+import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundScope
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/** Manages updates of the [Tile] assigned for the current custom tile. */
+@CustomTileBoundScope
+class CustomTileInteractor
+@Inject
+constructor(
+    private val user: UserHandle,
+    private val defaultsRepository: CustomTileDefaultsRepository,
+    private val customTileRepository: CustomTileRepository,
+    private val tileServiceManager: TileServiceManager,
+    @CustomTileBoundScope private val boundScope: CoroutineScope,
+    @Background private val backgroundContext: CoroutineContext,
+) {
+
+    private val tileUpdates =
+        MutableSharedFlow<Tile>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+
+    /** [Tile] updates. [updateTile] to emit a new one. */
+    val tiles: Flow<Tile>
+        get() = customTileRepository.getTiles(user)
+
+    /**
+     * Current [Tile]
+     *
+     * @throws IllegalStateException when the repository stores a tile for another user. This means
+     *   the tile hasn't been updated for the current user. Can happen when this is accessed before
+     *   [init] returns.
+     */
+    val tile: Tile
+        get() =
+            customTileRepository.getTile(user)
+                ?: throw IllegalStateException("Attempt to get a tile for a wrong user")
+
+    /**
+     * Initializes the repository for the current user. Suspends until it's safe to call [tile]
+     * which needs at least one of the following:
+     * - defaults are loaded;
+     * - receive tile update in [updateTile];
+     * - restoration happened for a persisted tile.
+     */
+    suspend fun init() {
+        launchUpdates()
+        customTileRepository.restoreForTheUserIfNeeded(user, tileServiceManager.isActiveTile)
+        // Suspend to make sure it gets the tile from one of the sources: restoration, defaults, or
+        // tile update.
+        customTileRepository.getTiles(user).firstOrNull()
+    }
+
+    private fun launchUpdates() {
+        tileUpdates
+            .onEach {
+                customTileRepository.updateWithTile(
+                    user,
+                    it,
+                    tileServiceManager.isActiveTile,
+                )
+            }
+            .flowOn(backgroundContext)
+            .launchIn(boundScope)
+        defaultsRepository
+            .defaults(user)
+            .onEach {
+                customTileRepository.updateWithDefaults(
+                    user,
+                    it,
+                    tileServiceManager.isActiveTile,
+                )
+            }
+            .flowOn(backgroundContext)
+            .launchIn(boundScope)
+    }
+
+    /** Updates current [Tile]. Emits a new event in [tiles]. */
+    fun updateTile(newTile: Tile) {
+        tileUpdates.tryEmit(newTile)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index b2b22646..881a6bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.tiles.impl.flashlight.domain
 
-import android.content.Context
+import android.content.res.Resources
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
@@ -26,30 +27,28 @@
 import javax.inject.Inject
 
 /** Maps [FlashlightTileModel] to [QSTileState]. */
-class FlashlightMapper @Inject constructor(private val context: Context) :
+class FlashlightMapper @Inject constructor(@Main private val resources: Resources) :
     QSTileDataToStateMapper<FlashlightTileModel> {
 
     override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
-        QSTileState.build(context, config.uiConfig) {
+        QSTileState.build(resources, config.uiConfig) {
             val icon =
-                Icon.Loaded(
-                    context.resources.getDrawable(
-                        if (data.isEnabled) {
-                            R.drawable.qs_flashlight_icon_on
-                        } else {
-                            R.drawable.qs_flashlight_icon_off
-                        }
-                    ),
+                Icon.Resource(
+                    if (data.isEnabled) {
+                        R.drawable.qs_flashlight_icon_on
+                    } else {
+                        R.drawable.qs_flashlight_icon_off
+                    },
                     contentDescription = null
                 )
             this.icon = { icon }
 
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
-                secondaryLabel = context.resources.getStringArray(R.array.tile_states_flashlight)[2]
+                secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[2]
             } else {
                 activationState = QSTileState.ActivationState.INACTIVE
-                secondaryLabel = context.resources.getStringArray(R.array.tile_states_flashlight)[1]
+                secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[1]
             }
             contentDescription = label
             supportedActions =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index 8e53723..7e7034d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.tiles.impl.location.domain
 
-import android.content.Context
+import android.content.res.Resources
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
@@ -26,32 +27,30 @@
 import javax.inject.Inject
 
 /** Maps [LocationTileModel] to [QSTileState]. */
-class LocationTileMapper @Inject constructor(private val context: Context) :
+class LocationTileMapper @Inject constructor(@Main private val resources: Resources) :
     QSTileDataToStateMapper<LocationTileModel> {
 
     override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
-        QSTileState.build(context, config.uiConfig) {
+        QSTileState.build(resources, config.uiConfig) {
             val icon =
-                Icon.Loaded(
-                    context.resources.getDrawable(
-                        if (data.isEnabled) {
-                            R.drawable.qs_location_icon_on
-                        } else {
-                            R.drawable.qs_location_icon_off
-                        }
-                    ),
+                Icon.Resource(
+                    if (data.isEnabled) {
+                        R.drawable.qs_location_icon_on
+                    } else {
+                        R.drawable.qs_location_icon_off
+                    },
                     contentDescription = null
                 )
             this.icon = { icon }
 
-            this.label = context.resources.getString(R.string.quick_settings_location_label)
+            this.label = resources.getString(R.string.quick_settings_location_label)
 
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
-                secondaryLabel = context.resources.getStringArray(R.array.tile_states_location)[2]
+                secondaryLabel = resources.getStringArray(R.array.tile_states_location)[2]
             } else {
                 activationState = QSTileState.ActivationState.INACTIVE
-                secondaryLabel = context.resources.getStringArray(R.array.tile_states_location)[1]
+                secondaryLabel = resources.getStringArray(R.array.tile_states_location)[1]
             }
             contentDescription = label
             supportedActions =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index f9e0b16..23e0cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.qs.tiles.viewmodel
 
-import android.content.Context
+import android.content.res.Resources
 import android.service.quicksettings.Tile
 import android.view.View
 import android.widget.Switch
@@ -46,13 +46,13 @@
     companion object {
 
         fun build(
-            context: Context,
+            resources: Resources,
             config: QSTileUIConfig,
             build: Builder.() -> Unit
         ): QSTileState =
             build(
                 { Icon.Resource(config.iconRes, null) },
-                context.getString(config.labelRes),
+                resources.getString(config.labelRes),
                 build,
             )
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index f3f9c91..d42fde6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -128,7 +128,7 @@
     private fun automaticallySwitchScenes() {
         applicationScope.launch {
             // TODO (b/308001302): Move this to a bouncer specific interactor.
-            bouncerInteractor.onImeHidden.collectLatest {
+            bouncerInteractor.onImeHiddenByUser.collectLatest {
                 if (sceneInteractor.desiredScene.value.key == SceneKey.Bouncer) {
                     sceneInteractor.changeScene(
                         scene = SceneModel(SceneKey.Lockscreen),
@@ -146,19 +146,21 @@
                     isAnySimLocked -> {
                         switchToScene(
                             targetSceneKey = SceneKey.Bouncer,
-                            loggingReason = "Need to authenticate locked sim card."
+                            loggingReason = "Need to authenticate locked SIM card."
                         )
                     }
-                    isUnlocked && !canSwipeToEnter -> {
+                    isUnlocked && canSwipeToEnter == false -> {
                         switchToScene(
                             targetSceneKey = SceneKey.Gone,
-                            loggingReason = "Sim cards are unlocked."
+                            loggingReason = "All SIM cards unlocked and device already" +
+                                " unlocked and lockscreen doesn't require a swipe to dismiss."
                         )
                     }
                     else -> {
                         switchToScene(
                             targetSceneKey = SceneKey.Lockscreen,
-                            loggingReason = "Sim cards are unlocked."
+                            loggingReason = "All SIM cards unlocked and device still locked" +
+                                " or lockscreen still requires a swipe to dismiss."
                         )
                     }
                 }
@@ -205,11 +207,17 @@
                             //    when the user is passively authenticated, the false value here
                             //    when the unlock state changes indicates this is an active
                             //    authentication attempt.
-                            if (isBypassEnabled || !canSwipeToEnter)
-                                SceneKey.Gone to
-                                    "device has been unlocked on lockscreen with either " +
-                                        "bypass enabled or using an active authentication mechanism"
-                            else null
+                            when {
+                                isBypassEnabled ->
+                                    SceneKey.Gone to
+                                        "device has been unlocked on lockscreen with bypass" +
+                                            " enabled"
+                                canSwipeToEnter == false ->
+                                    SceneKey.Gone to
+                                        "device has been unlocked on lockscreen using an active" +
+                                            " authentication mechanism"
+                                else -> null
+                            }
                         // Not on lockscreen or bouncer, so remain in the current scene.
                         else -> null
                     }
@@ -232,7 +240,7 @@
                 } else {
                     val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
                     val isUnlocked = deviceEntryInteractor.isUnlocked.value
-                    if (isUnlocked && !canSwipeToEnter) {
+                    if (isUnlocked && canSwipeToEnter == false) {
                         switchToScene(
                             targetSceneKey = SceneKey.Gone,
                             loggingReason =
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index d14ef35..dbb58a3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.scene.shared.flag
 
+import android.content.Context
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.Flags.sceneContainer
 import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flag
 import com.android.systemui.flags.Flags
@@ -29,6 +31,7 @@
 import com.android.systemui.flags.ResourceBooleanFlag
 import com.android.systemui.flags.UnreleasedFlag
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
+import com.android.systemui.res.R
 import dagger.Module
 import dagger.Provides
 import dagger.assisted.Assisted
@@ -51,6 +54,7 @@
 class SceneContainerFlagsImpl
 @AssistedInject
 constructor(
+    @Application private val context: Context,
     private val featureFlagsClassic: FeatureFlagsClassic,
     @Assisted private val isComposeAvailable: Boolean,
 ) : SceneContainerFlags {
@@ -80,7 +84,11 @@
             ),
         ) +
             classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
-            listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled())
+            listOf(
+                ComposeMustBeAvailable(),
+                CompileTimeFlagMustBeEnabled(),
+                ResourceConfigMustBeEnabled()
+            )
 
     override fun isEnabled(): Boolean {
         // SCENE_CONTAINER_ENABLED is an explicit static flag check that helps with downstream
@@ -146,6 +154,14 @@
         }
     }
 
+    private inner class ResourceConfigMustBeEnabled : Requirement {
+        override val name: String = "R.bool.config_sceneContainerFrameworkEnabled must be true"
+
+        override fun isMet(): Boolean {
+            return context.resources.getBoolean(R.bool.config_sceneContainerFrameworkEnabled)
+        }
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(isComposeAvailable: Boolean): SceneContainerFlagsImpl
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index e7481cc..b98093e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -32,13 +32,16 @@
 import android.os.Looper;
 import android.os.ResultReceiver;
 import android.view.Gravity;
+import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ArrayAdapter;
 import android.widget.Spinner;
 import android.widget.Switch;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
@@ -115,6 +118,17 @@
         mOptions.setOnItemClickListenerInt((parent, view, position, id) -> {
             mAudioSwitch.setChecked(true);
         });
+
+        // disable redundant Touch & Hold accessibility action for Switch Access
+        mOptions.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+            @Override
+            public void onInitializeAccessibilityNodeInfo(@NonNull View host,
+                    @NonNull AccessibilityNodeInfo info) {
+                info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
+                super.onInitializeAccessibilityNodeInfo(host, info);
+            }
+        });
+        mOptions.setLongClickable(false);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 9f4ea27..d13edf0 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -39,6 +39,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -58,18 +59,21 @@
     private final DelayableExecutor mMainExecutor;
     private final AccessibilityManagerWrapper mAccessibilityMgr;
     private Runnable mCancelTimeoutRunnable;
+    private final ShadeInteractor mShadeInteractor;
 
     @Inject
     public BrightnessDialog(
             BrightnessSliderController.Factory brightnessSliderfactory,
             BrightnessController.Factory brightnessControllerFactory,
             @Main DelayableExecutor mainExecutor,
-            AccessibilityManagerWrapper accessibilityMgr
+            AccessibilityManagerWrapper accessibilityMgr,
+            ShadeInteractor shadeInteractor
     ) {
         mToggleSliderFactory = brightnessSliderfactory;
         mBrightnessControllerFactory = brightnessControllerFactory;
         mMainExecutor = mainExecutor;
         mAccessibilityMgr = accessibilityMgr;
+        mShadeInteractor = shadeInteractor;
     }
 
 
@@ -79,6 +83,10 @@
         setWindowAttributes();
         setContentView(R.layout.brightness_mirror_container);
         setBrightnessDialogViewAttributes();
+
+        if (mShadeInteractor.isQsExpanded().getValue()) {
+            finish();
+        }
     }
 
     private void setWindowAttributes() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 2a071de..0065db3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -66,10 +66,10 @@
 
     private fun upDestinationSceneKey(
         isUnlocked: Boolean,
-        canSwipeToDismiss: Boolean,
+        canSwipeToDismiss: Boolean?,
     ): SceneKey {
         return when {
-            canSwipeToDismiss -> SceneKey.Lockscreen
+            canSwipeToDismiss == true -> SceneKey.Lockscreen
             isUnlocked -> SceneKey.Gone
             else -> SceneKey.Lockscreen
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 1b096b5..c1a630f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -612,7 +612,7 @@
     private static KeyboardShortcutMultiMappingGroup getMultiMappingInputShortcuts(
             Context context) {
         List<ShortcutMultiMappingInfo> shortcutMultiMappingInfoList = Arrays.asList(
-                /* Switch input language (next language): Ctrl + Space or Meta + Space */
+                /* Switch input language (next language): Ctrl + Space */
                 new ShortcutMultiMappingInfo(
                         context.getString(R.string.input_switch_input_language_next),
                         null,
@@ -621,14 +621,9 @@
                                         context.getString(
                                                 R.string.input_switch_input_language_next),
                                         KeyEvent.KEYCODE_SPACE, KeyEvent.META_CTRL_ON),
-                                        null),
-                                new ShortcutKeyGroup(new KeyboardShortcutInfo(
-                                        context.getString(
-                                                R.string.input_switch_input_language_next),
-                                        KeyEvent.KEYCODE_SPACE, KeyEvent.META_META_ON),
                                         null))),
                 /* Switch input language (previous language): */
-                /* Ctrl + Shift + Space or Meta + Shift + Space */
+                /* Ctrl + Shift + Space */
                 new ShortcutMultiMappingInfo(
                         context.getString(R.string.input_switch_input_language_previous),
                         null,
@@ -638,12 +633,6 @@
                                                 R.string.input_switch_input_language_previous),
                                         KeyEvent.KEYCODE_SPACE,
                                         KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON),
-                                        null),
-                                new ShortcutKeyGroup(new KeyboardShortcutInfo(
-                                        context.getString(
-                                                R.string.input_switch_input_language_previous),
-                                        KeyEvent.KEYCODE_SPACE,
-                                        KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON),
                                         null)))
         );
         return new KeyboardShortcutMultiMappingGroup(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 0e83c78..e486457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -530,16 +530,12 @@
                 userHandle = mCurrentUserId;
             }
             if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
-                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
-                // default value before moving to 'released'
-                Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable());
-                updateUserShowPrivateSettings(userHandle);
+                Log.i(TAG, "Asking for redact notifs setting too early", new Throwable());
+                return false;
             }
             if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
-                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
-                // default value before moving to 'released'
-                Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable());
-                updateDpcSettings(userHandle);
+                Log.i(TAG, "Asking for redact notifs dpm override too early", new Throwable());
+                return false;
             }
             return mUsersUsersAllowingPrivateNotifications.get(userHandle)
                     && mUsersDpcAllowingPrivateNotifications.get(userHandle);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 64f61d9..8eda96f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -27,7 +27,6 @@
 import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -43,11 +42,9 @@
  * A view that can be used for both the dimmed and normal background of an notification.
  */
 public class NotificationBackgroundView extends View implements Dumpable {
-    private static final String TAG = "NotificationBackgroundView";
 
     private final boolean mDontModifyCorners;
     private Drawable mBackground;
-    private Drawable mBackgroundDrawableToTint;
     private int mClipTopAmount;
     private int mClipBottomAmount;
     private int mTintColor;
@@ -134,7 +131,6 @@
             unscheduleDrawable(mBackground);
         }
         mBackground = background;
-        mBackgroundDrawableToTint = findBackgroundDrawableToTint(mBackground);
         mRippleColor = null;
         mBackground.mutate();
         if (mBackground != null) {
@@ -148,46 +144,25 @@
         invalidate();
     }
 
-    // setCustomBackground should be called from ActivatableNotificationView.initBackground
-    // with R.drawable.notification_material_bg, which is a layer-list with a lower layer
-    // for the background color (annotated with an ID so we can find it) and an upper layer
-    // to blend in the stateful @color/notification_overlay_color.
-    //
-    // If the notification is tinted, we want to set a tint list on *just that lower layer* that
-    // will replace the default materialColorSurfaceContainerHigh *without* wiping out the stateful
-    // tints in the upper layer that make the hovered and pressed states visible.
-    //
-    // This function fishes that lower layer out, or makes a fuss in logcat if it can't find it.
-    private @Nullable Drawable findBackgroundDrawableToTint(@Nullable Drawable background) {
-        if (background == null) {
-            return null;
-        }
-
-        if (!(background instanceof LayerDrawable)) {
-            Log.wtf(TAG, "background is not a LayerDrawable: " + background);
-            return background;
-        }
-
-        final Drawable backgroundColorLayer = ((LayerDrawable) background).findDrawableByLayerId(
-                R.id.notification_background_color_layer);
-
-        if (backgroundColorLayer == null) {
-            Log.wtf(TAG, "background is missing background color layer: " + background);
-            return background;
-        }
-
-        return backgroundColorLayer;
-    }
-
     public void setCustomBackground(int drawableResId) {
         final Drawable d = mContext.getDrawable(drawableResId);
         setCustomBackground(d);
     }
 
     public void setTint(int tintColor) {
-        mBackgroundDrawableToTint.setTint(tintColor);
-        mBackgroundDrawableToTint.setTintMode(PorterDuff.Mode.SRC_ATOP);
+        if (tintColor != 0) {
+            ColorStateList stateList = new ColorStateList(new int[][]{
+                    new int[]{com.android.internal.R.attr.state_pressed},
+                    new int[]{com.android.internal.R.attr.state_hovered},
+                    new int[]{}},
 
+                    new int[]{tintColor, tintColor, tintColor}
+            );
+            mBackground.setTintMode(PorterDuff.Mode.SRC_ATOP);
+            mBackground.setTintList(stateList);
+        } else {
+            mBackground.setTintList(null);
+        }
         mTintColor = tintColor;
         invalidate();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index 6d8ec44..c615887 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -67,6 +67,7 @@
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
 
     private boolean mIsStatusBarExpanded = false;
+    private boolean mIsSceneContainerVisible = false;
     private boolean mShouldAdjustInsets = false;
     private View mNotificationShadeWindowView;
     private View mNotificationPanelView;
@@ -128,11 +129,14 @@
         });
 
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
-        javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded);
 
         if (sceneContainerFlags.isEnabled()) {
             javaAdapter.alwaysCollectFlow(
                     sceneInteractor.get().isVisible(),
+                    this::onSceneContainerVisibilityChanged);
+        } else {
+            javaAdapter.alwaysCollectFlow(
+                    shadeInteractor.isAnyExpanded(),
                     this::onShadeOrQsExpanded);
         }
 
@@ -164,6 +168,17 @@
         }
     }
 
+    private void onSceneContainerVisibilityChanged(Boolean isVisible) {
+        if (isVisible != mIsSceneContainerVisible) {
+            mIsSceneContainerVisible = isVisible;
+            if (isVisible) {
+                // make sure our state is sensible
+                mForceCollapsedUntilLayout = false;
+            }
+            updateTouchableRegion();
+        }
+    }
+
     /**
      * Calculates the touch region needed for heads up notifications, taking into consideration
      * any existing display cutouts (notch)
@@ -267,6 +282,7 @@
         // since we don't want stray touches to go through the light reveal scrim to whatever is
         // underneath.
         return mIsStatusBarExpanded
+                || mIsSceneContainerVisible
                 || mPrimaryBouncerInteractor.isShowing().getValue()
                 || mAlternateBouncerInteractor.isVisibleState()
                 || mUnlockedScreenOffAnimationController.isAnimationPlaying();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java
index bbba19d..87df180 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java
@@ -73,7 +73,11 @@
 
     /** Callback to be notified about device posture changes. */
     interface Callback {
-        /** Called when the posture changes. */
+        /**
+         * Called when the posture changes. If there are multiple active displays ("concurrent"),
+         * this will report the physical posture of the device (also known as the base device
+         * state).
+         */
         void onPostureChanged(@DevicePostureInt int posture);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
index 8f1ac81..422aa4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
@@ -39,8 +39,11 @@
 /** Implementation of {@link DevicePostureController} using the DeviceStateManager. */
 @SysUISingleton
 public class DevicePostureControllerImpl implements DevicePostureController {
+    /** From androidx.window.common.COMMON_STATE_USE_BASE_STATE */
+    private static final int COMMON_STATE_USE_BASE_STATE = 1000;
     private final List<Callback> mListeners = new ArrayList<>();
     private int mCurrentDevicePosture = DEVICE_POSTURE_UNKNOWN;
+    private int mCurrentBasePosture = DEVICE_POSTURE_UNKNOWN;
 
     private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
 
@@ -73,16 +76,32 @@
             mDeviceStateToPostureMap.put(deviceState, posture);
         }
 
-        deviceStateManager.registerCallback(executor, state -> {
-            Assert.isMainThread();
-            mCurrentDevicePosture =
-                    mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN);
+        deviceStateManager.registerCallback(executor, new DeviceStateManager.DeviceStateCallback() {
+            @Override
+            public void onStateChanged(int state) {
+                Assert.isMainThread();
+                mCurrentDevicePosture =
+                        mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN);
+                sendUpdatePosture();
+            }
 
-            ListenersTracing.INSTANCE.forEachTraced(mListeners, "DevicePostureControllerImpl",
+            @Override
+            public void onBaseStateChanged(int state) {
+                Assert.isMainThread();
+                mCurrentBasePosture = mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN);
+
+                if (useBaseState()) {
+                    sendUpdatePosture();
+                }
+            }
+
+            private void sendUpdatePosture() {
+                ListenersTracing.INSTANCE.forEachTraced(mListeners, "DevicePostureControllerImpl",
                     l -> {
-                        l.onPostureChanged(mCurrentDevicePosture);
+                        l.onPostureChanged(getDevicePosture());
                         return Unit.INSTANCE;
                     });
+            }
         });
     }
 
@@ -100,6 +119,14 @@
 
     @Override
     public int getDevicePosture() {
-        return mCurrentDevicePosture;
+        if (useBaseState()) {
+            return mCurrentBasePosture;
+        } else {
+            return mCurrentDevicePosture;
+        }
+    }
+
+    private boolean useBaseState() {
+        return mCurrentDevicePosture == COMMON_STATE_USE_BASE_STATE;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
index e576f36..279e5ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -16,6 +16,7 @@
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
 
 public interface FlashlightController extends CallbackController<FlashlightListener>, Dumpable {
 
@@ -24,6 +25,7 @@
     boolean isAvailable();
     boolean isEnabled();
 
+    @WeaklyReferencedCallback
     public interface FlashlightListener {
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 0531487..9689811 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -20,10 +20,12 @@
 import android.hardware.devicestate.DeviceStateManager
 import android.os.SystemProperties
 import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.LifecycleScreenStatusProvider
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldBgProgressFlag
 import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
@@ -64,6 +66,10 @@
 
     @Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui"
 
+    @Provides
+    @UnfoldBgProgressFlag
+    fun unfoldBgProgressFlag() = Flags.unfoldAnimationBackgroundProgress()
+
     /** A globally available FoldStateListener that allows one to query the fold state. */
     @Provides
     @Singleton
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index d8799e1..4395282 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -133,8 +133,8 @@
     }
 
     @Test
-    public void setScale() throws RemoteException {
-        mIWindowMagnificationConnection.setScale(TEST_DISPLAY, 3.0f);
+    public void setScaleForWindowMagnification() throws RemoteException {
+        mIWindowMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f);
         waitForIdleSync();
 
         verify(mWindowMagnificationController).setScale(3.0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index f4122d5..ea20d29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -338,6 +338,13 @@
         waitForIdleSync()
 
         assertThat(container.hasCredentialView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+
+        // Check credential view persists after new attachment
+        container.onAttachedToWindow()
+
+        assertThat(container.hasCredentialView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt
index a9f8ea0..81d02b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt
@@ -85,7 +85,7 @@
         `when`(sharedPreferences.edit()).thenReturn(editor)
 
         tile = Tile()
-        customTileStatePersister = CustomTileStatePersister(mockContext)
+        customTileStatePersister = CustomTileStatePersisterImpl(mockContext)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
index 3808c7e..313ccb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
@@ -228,16 +228,16 @@
                 showPairNewDevice = true
             )
 
-            val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group)
-            val pairNewLayout =
-                bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group)
+            val seeAllButton = bluetoothTileDialog.requireViewById<View>(R.id.see_all_button)
+            val pairNewButton =
+                bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_button)
             val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
             val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter
 
-            assertThat(seeAllLayout).isNotNull()
-            assertThat(seeAllLayout.visibility).isEqualTo(GONE)
-            assertThat(pairNewLayout).isNotNull()
-            assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE)
+            assertThat(seeAllButton).isNotNull()
+            assertThat(seeAllButton.visibility).isEqualTo(GONE)
+            assertThat(pairNewButton).isNotNull()
+            assertThat(pairNewButton.visibility).isEqualTo(VISIBLE)
             assertThat(adapter.itemCount).isEqualTo(1)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
index fb5dd21..99993f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -113,9 +112,7 @@
         testScope.runTest {
             bluetoothTileDialogViewModel.showDialog(context, null)
 
-            assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
             verify(dialogLaunchAnimator, never()).showFromView(any(), any(), any(), any())
-            assertThat(bluetoothTileDialogViewModel.dialog?.isShowing).isTrue()
             verify(uiEventLogger).log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN)
         }
     }
@@ -125,7 +122,6 @@
         testScope.runTest {
             bluetoothTileDialogViewModel.showDialog(mContext, LinearLayout(mContext))
 
-            assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
             verify(dialogLaunchAnimator).showFromView(any(), any(), nullable(), anyBoolean())
         }
     }
@@ -136,7 +132,6 @@
             backgroundExecutor.execute {
                 bluetoothTileDialogViewModel.showDialog(mContext, LinearLayout(mContext))
 
-                assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
                 verify(dialogLaunchAnimator).showFromView(any(), any(), nullable(), anyBoolean())
             }
         }
@@ -147,7 +142,6 @@
         testScope.runTest {
             bluetoothTileDialogViewModel.showDialog(context, null)
 
-            assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
             verify(deviceItemInteractor).deviceItemUpdate
         }
     }
@@ -157,7 +151,6 @@
         testScope.runTest {
             bluetoothTileDialogViewModel.showDialog(context, null)
 
-            assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
             verify(bluetoothStateInteractor).bluetoothStateUpdate
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
index 4c173cc..e236f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
@@ -220,45 +220,57 @@
 
     @Test
     fun testUpdateDeviceItemOnClick_connectedMedia_setActive() {
-        `when`(deviceItem1.type).thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
+        testScope.runTest {
+            `when`(deviceItem1.type).thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
 
-        interactor.updateDeviceItemOnClick(deviceItem1)
+            interactor.updateDeviceItemOnClick(deviceItem1)
 
-        verify(cachedDevice1).setActive()
-        verify(logger)
-            .logDeviceClick(cachedDevice1.address, DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
+            verify(cachedDevice1).setActive()
+            verify(logger)
+                .logDeviceClick(
+                    cachedDevice1.address,
+                    DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE
+                )
+        }
     }
 
     @Test
     fun testUpdateDeviceItemOnClick_activeMedia_disconnect() {
-        `when`(deviceItem1.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+        testScope.runTest {
+            `when`(deviceItem1.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
 
-        interactor.updateDeviceItemOnClick(deviceItem1)
+            interactor.updateDeviceItemOnClick(deviceItem1)
 
-        verify(cachedDevice1).disconnect()
-        verify(logger)
-            .logDeviceClick(cachedDevice1.address, DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+            verify(cachedDevice1).disconnect()
+            verify(logger)
+                .logDeviceClick(cachedDevice1.address, DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+        }
     }
 
     @Test
     fun testUpdateDeviceItemOnClick_connectedOtherDevice_disconnect() {
-        `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+        testScope.runTest {
+            `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
 
-        interactor.updateDeviceItemOnClick(deviceItem1)
+            interactor.updateDeviceItemOnClick(deviceItem1)
 
-        verify(cachedDevice1).disconnect()
-        verify(logger)
-            .logDeviceClick(cachedDevice1.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+            verify(cachedDevice1).disconnect()
+            verify(logger)
+                .logDeviceClick(cachedDevice1.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+        }
     }
 
     @Test
     fun testUpdateDeviceItemOnClick_saved_connect() {
-        `when`(deviceItem1.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+        testScope.runTest {
+            `when`(deviceItem1.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
 
-        interactor.updateDeviceItemOnClick(deviceItem1)
+            interactor.updateDeviceItemOnClick(deviceItem1)
 
-        verify(cachedDevice1).connect()
-        verify(logger).logDeviceClick(cachedDevice1.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+            verify(cachedDevice1).connect()
+            verify(logger)
+                .logDeviceClick(cachedDevice1.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+        }
     }
 
     private fun createFactory(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 5969bd8..0173c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.flags.ResourceBooleanFlag
 import com.android.systemui.flags.UnreleasedFlag
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -74,10 +75,15 @@
             .forEach { flagToken ->
                 setFlagsRule.enableFlags(flagToken)
                 aconfigFlags.setFlag(flagToken, testCase.areAllFlagsSet)
+                overrideResource(
+                    R.bool.config_sceneContainerFrameworkEnabled,
+                    testCase.isResourceConfigEnabled
+                )
             }
 
         underTest =
             SceneContainerFlagsImpl(
+                context = context,
                 featureFlagsClassic = featureFlags,
                 isComposeAvailable = testCase.isComposeAvailable,
             )
@@ -91,13 +97,12 @@
     internal data class TestCase(
         val isComposeAvailable: Boolean,
         val areAllFlagsSet: Boolean,
+        val isResourceConfigEnabled: Boolean,
         val expectedEnabled: Boolean,
     ) {
         override fun toString(): String {
-            return """
-                (compose=$isComposeAvailable + flags=$areAllFlagsSet) -> expected=$expectedEnabled
-            """
-                .trimIndent()
+            return "(compose=$isComposeAvailable + flags=$areAllFlagsSet) + XML" +
+                " config=$isResourceConfigEnabled -> expected=$expectedEnabled"
         }
     }
 
@@ -105,17 +110,20 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun testCases() = buildList {
-            repeat(4) { combination ->
-                val isComposeAvailable = combination and 0b10 != 0
-                val areAllFlagsSet = combination and 0b01 != 0
+            repeat(8) { combination ->
+                val isComposeAvailable = combination and 0b100 != 0
+                val areAllFlagsSet = combination and 0b010 != 0
+                val isResourceConfigEnabled = combination and 0b001 != 0
 
-                val expectedEnabled = isComposeAvailable && areAllFlagsSet
+                val expectedEnabled =
+                    isComposeAvailable && areAllFlagsSet && isResourceConfigEnabled
 
                 add(
                     TestCase(
                         isComposeAvailable = isComposeAvailable,
                         areAllFlagsSet = areAllFlagsSet,
                         expectedEnabled = expectedEnabled,
+                        isResourceConfigEnabled = isResourceConfigEnabled,
                     )
                 )
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 6e487cd..88c728f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -28,12 +28,16 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -55,6 +59,8 @@
     @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
     @Mock private lateinit var brightnessController: BrightnessController
     @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
+    @Mock private lateinit var shadeInteractorLazy: Lazy<ShadeInteractor>
+    @Mock private lateinit var shadeInteractor: ShadeInteractor
 
     private val clock = FakeSystemClock()
     private val mainExecutor = FakeExecutor(clock)
@@ -68,7 +74,8 @@
                     brightnessSliderControllerFactory,
                     brightnessControllerFactory,
                     mainExecutor,
-                    accessibilityMgr
+                    accessibilityMgr,
+                    shadeInteractor
                 )
             },
             /* initialTouchMode= */ false,
@@ -82,6 +89,8 @@
             .thenReturn(brightnessSliderController)
         `when`(brightnessSliderController.rootView).thenReturn(View(context))
         `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
+        whenever(shadeInteractorLazy.get()).thenReturn(shadeInteractor)
+        whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
     }
 
     @After
@@ -175,13 +184,15 @@
         brightnessSliderControllerFactory: BrightnessSliderController.Factory,
         brightnessControllerFactory: BrightnessController.Factory,
         mainExecutor: DelayableExecutor,
-        accessibilityMgr: AccessibilityManagerWrapper
+        accessibilityMgr: AccessibilityManagerWrapper,
+        shadeInteractor: ShadeInteractor
     ) :
         BrightnessDialog(
             brightnessSliderControllerFactory,
             brightnessControllerFactory,
             mainExecutor,
-            accessibilityMgr
+            accessibilityMgr,
+            shadeInteractor
         ) {
         private var finishing = false
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
new file mode 100644
index 0000000..ce47170
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.hardware.devicestate.DeviceStateManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_FLIPPED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
+import com.android.systemui.statusbar.policy.DevicePostureController.SUPPORTED_POSTURES_SIZE
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class DevicePostureControllerImplTest : SysuiTestCase() {
+    private val useBaseStateDeviceState = SUPPORTED_POSTURES_SIZE
+    private val deviceStateToPostureMapping =
+        arrayOf(
+            "$DEVICE_POSTURE_UNKNOWN:$DEVICE_POSTURE_UNKNOWN",
+            "$DEVICE_POSTURE_CLOSED:$DEVICE_POSTURE_CLOSED",
+            "$DEVICE_POSTURE_HALF_OPENED:$DEVICE_POSTURE_HALF_OPENED",
+            "$DEVICE_POSTURE_OPENED:$DEVICE_POSTURE_OPENED",
+            "$DEVICE_POSTURE_FLIPPED:$DEVICE_POSTURE_FLIPPED",
+            "$useBaseStateDeviceState:1000"
+        )
+    @Mock private lateinit var deviceStateManager: DeviceStateManager
+    @Captor
+    private lateinit var deviceStateCallback: ArgumentCaptor<DeviceStateManager.DeviceStateCallback>
+
+    private lateinit var mainExecutor: FakeExecutor
+    private lateinit var testableResources: TestableResources
+    private lateinit var underTest: DevicePostureControllerImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        mainExecutor = FakeExecutor(FakeSystemClock())
+        testableResources = context.getOrCreateTestableResources()
+        testableResources.addOverride(
+            com.android.internal.R.array.config_device_state_postures,
+            deviceStateToPostureMapping
+        )
+        underTest =
+            DevicePostureControllerImpl(
+                context,
+                deviceStateManager,
+                mainExecutor,
+            )
+        verifyRegistersForDeviceStateCallback()
+    }
+
+    @Test
+    fun testPostureChanged_updates() {
+        var posture = -1
+        underTest.addCallback { posture = it }
+
+        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_UNKNOWN)
+        assertThat(posture).isEqualTo(DEVICE_POSTURE_UNKNOWN)
+
+        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_CLOSED)
+        assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED)
+
+        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED)
+        assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED)
+
+        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_OPENED)
+        assertThat(posture).isEqualTo(DEVICE_POSTURE_OPENED)
+
+        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_FLIPPED)
+        assertThat(posture).isEqualTo(DEVICE_POSTURE_FLIPPED)
+    }
+
+    @Test
+    fun testPostureChanged_useBaseUpdate() {
+        var posture = -1
+        underTest.addCallback { posture = it }
+
+        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED)
+        assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED)
+
+        // base state change doesn't change the posture
+        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED)
+        assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED)
+
+        // WHEN the display state maps to using the base state, then posture updates
+        deviceStateCallback.value.onStateChanged(useBaseStateDeviceState)
+        assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED)
+    }
+
+    @Test
+    fun baseStateChanges_doesNotUpdatePosture() {
+        var numPostureChanges = 0
+        underTest.addCallback { numPostureChanges++ }
+
+        deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED)
+        assertThat(numPostureChanges).isEqualTo(1)
+
+        // base state changes doesn't send another posture update since the device state isn't
+        // useBaseStateDeviceState
+        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED)
+        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_HALF_OPENED)
+        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_FLIPPED)
+        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_OPENED)
+        deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_UNKNOWN)
+        assertThat(numPostureChanges).isEqualTo(1)
+    }
+
+    private fun verifyRegistersForDeviceStateCallback() {
+        verify(deviceStateManager).registerCallback(eq(mainExecutor), deviceStateCallback.capture())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
new file mode 100644
index 0000000..4e61b89
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.unfold.progress
+
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.utils.os.FakeHandler
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+class MainThreadUnfoldTransitionProgressProviderTest : SysuiTestCase() {
+
+    private val wrappedProgressProvider = TestUnfoldTransitionProvider()
+    private val fakeHandler = FakeHandler(Looper.getMainLooper())
+    private val listener = TestUnfoldProgressListener()
+
+    private val progressProvider =
+        MainThreadUnfoldTransitionProgressProvider(fakeHandler, wrappedProgressProvider)
+
+    @Test
+    fun onTransitionStarted_propagated() {
+        progressProvider.addCallback(listener)
+
+        wrappedProgressProvider.onTransitionStarted()
+        fakeHandler.dispatchQueuedMessages()
+
+        listener.assertStarted()
+    }
+
+    @Test
+    fun onTransitionProgress_propagated() {
+        progressProvider.addCallback(listener)
+
+        wrappedProgressProvider.onTransitionStarted()
+        wrappedProgressProvider.onTransitionProgress(0.5f)
+        fakeHandler.dispatchQueuedMessages()
+
+        listener.assertLastProgress(0.5f)
+    }
+
+    @Test
+    fun onTransitionFinished_propagated() {
+        progressProvider.addCallback(listener)
+
+        wrappedProgressProvider.onTransitionStarted()
+        wrappedProgressProvider.onTransitionProgress(0.5f)
+        wrappedProgressProvider.onTransitionFinished()
+        fakeHandler.dispatchQueuedMessages()
+
+        listener.ensureTransitionFinished()
+    }
+
+    @Test
+    fun onTransitionFinishing_propagated() {
+        progressProvider.addCallback(listener)
+
+        wrappedProgressProvider.onTransitionStarted()
+        wrappedProgressProvider.onTransitionProgress(0.5f)
+        wrappedProgressProvider.onTransitionFinished()
+        fakeHandler.dispatchQueuedMessages()
+
+        listener.ensureTransitionFinished()
+    }
+
+    @Test
+    fun onTransitionStarted_afterCallbackRemoved_notPropagated() {
+        progressProvider.addCallback(listener)
+        progressProvider.removeCallback(listener)
+
+        wrappedProgressProvider.onTransitionStarted()
+        fakeHandler.dispatchQueuedMessages()
+
+        listener.assertNotStarted()
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.kt
new file mode 100644
index 0000000..29702eb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeCustomTileStatePersister.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.service.quicksettings.Tile
+
+class FakeCustomTileStatePersister : CustomTileStatePersister {
+
+    private val tiles: MutableMap<TileServiceKey, Tile> = mutableMapOf()
+
+    override fun readState(key: TileServiceKey): Tile? = tiles[key]
+
+    override fun persistState(key: TileServiceKey, tile: Tile) {
+        tiles[key] = tile
+    }
+
+    override fun removeState(key: TileServiceKey) {
+        tiles.remove(key)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt
new file mode 100644
index 0000000..d2351dc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom
+
+import android.service.quicksettings.Tile
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.tiles
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
+
+/**
+ * [Tile]-specific extension for [Truth]. Use [assertThat] or [tiles] to get an instance of this
+ * subject.
+ */
+class TileSubject private constructor(failureMetadata: FailureMetadata, subject: Tile?) :
+    Subject(failureMetadata, subject) {
+
+    private val actual: Tile? = subject
+
+    /** Asserts if the [Tile] fields are the same. */
+    fun isEqualTo(other: Tile?) {
+        if (actual == null) {
+            check("other").that(other).isNull()
+            return
+        } else {
+            check("other").that(other).isNotNull()
+            other ?: return
+        }
+
+        check("icon").that(actual.icon).isEqualTo(other.icon)
+        check("label").that(actual.label).isEqualTo(other.label)
+        check("subtitle").that(actual.subtitle).isEqualTo(other.subtitle)
+        check("contentDescription")
+            .that(actual.contentDescription)
+            .isEqualTo(other.contentDescription)
+        check("stateDescription").that(actual.stateDescription).isEqualTo(other.stateDescription)
+        check("activityLaunchForClick")
+            .that(actual.activityLaunchForClick)
+            .isEqualTo(other.activityLaunchForClick)
+        check("state").that(actual.state).isEqualTo(other.state)
+    }
+
+    companion object {
+
+        /** Returns a factory to be used with [Truth.assertAbout]. */
+        fun tiles(): Factory<TileSubject, Tile?> {
+            return Factory { failureMetadata: FailureMetadata, subject: Tile? ->
+                TileSubject(failureMetadata, subject)
+            }
+        }
+
+        /** Shortcut for `Truth.assertAbout(tiles()).that(tile)`. */
+        fun assertThat(tile: Tile?): TileSubject = Truth.assertAbout(tiles()).that(tile)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt
index 13910fd..ccba072 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileDefaultsRepository.kt
@@ -19,15 +19,20 @@
 import android.content.ComponentName
 import android.os.UserHandle
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
 
 class FakeCustomTileDefaultsRepository : CustomTileDefaultsRepository {
 
     private val defaults: MutableMap<DefaultsKey, CustomTileDefaults> = mutableMapOf()
-    private val defaultsFlow = MutableSharedFlow<DefaultsRequest>()
+    private val defaultsFlow =
+        MutableSharedFlow<DefaultsRequest>(
+            replay = 1,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST
+        )
 
     private val mutableDefaultsRequests: MutableList<DefaultsRequest> = mutableListOf()
     val defaultsRequests: List<DefaultsRequest> = mutableDefaultsRequests
@@ -41,7 +46,7 @@
                     old == new
                 }
             }
-            .map { defaults[DefaultsKey(it.user, it.componentName)]!! }
+            .mapNotNull { defaults[DefaultsKey(it.user, it.componentName)] }
 
     override fun requestNewDefaults(
         user: UserHandle,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
new file mode 100644
index 0000000..ccf0391
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.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.qs.tiles.impl.custom.data.repository
+
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+
+class FakeCustomTileRepository(
+    tileSpec: TileSpec.CustomTileSpec,
+    customTileStatePersister: FakeCustomTileStatePersister,
+    testBackgroundContext: CoroutineContext,
+) : CustomTileRepository {
+
+    private val realDelegate: CustomTileRepository =
+        CustomTileRepositoryImpl(
+            tileSpec,
+            customTileStatePersister,
+            testBackgroundContext,
+        )
+
+    override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) =
+        realDelegate.restoreForTheUserIfNeeded(user, isPersistable)
+
+    override fun getTiles(user: UserHandle): Flow<Tile> = realDelegate.getTiles(user)
+
+    override fun getTile(user: UserHandle): Tile? = realDelegate.getTile(user)
+
+    override suspend fun updateWithTile(
+        user: UserHandle,
+        newTile: Tile,
+        isPersistable: Boolean,
+    ) = realDelegate.updateWithTile(user, newTile, isPersistable)
+
+    override suspend fun updateWithDefaults(
+        user: UserHandle,
+        defaults: CustomTileDefaults,
+        isPersistable: Boolean,
+    ) = realDelegate.updateWithDefaults(user, defaults, isPersistable)
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 42d31b3..f7fb014 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -19,8 +19,10 @@
 import android.os.Handler
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
 import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.unfold.dagger.UnfoldBgProgressFlag
 import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
+import com.android.systemui.unfold.progress.MainThreadUnfoldTransitionProgressProvider
 import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
 import com.android.systemui.unfold.updates.DeviceFoldStateProvider
@@ -36,15 +38,18 @@
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManagerImpl
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider
+import dagger.BindsOptionalOf
 import dagger.Module
 import dagger.Provides
 import java.util.Optional
 import javax.inject.Provider
 import javax.inject.Singleton
+import kotlin.jvm.optionals.getOrDefault
 
 @Module(
     includes =
         [
+            UnfoldFlagsModule::class,
             UnfoldSharedInternalModule::class,
             UnfoldRotationProviderInternalModule::class,
             HingeAngleProviderInternalModule::class,
@@ -69,6 +74,16 @@
     fun foldStateRepository(impl: FoldStateRepositoryImpl): FoldStateRepository = impl
 }
 
+@Module
+abstract class UnfoldFlagsModule {
+    /**
+     * Users of the library can bind this boolean to notify whether the progress should be
+     * calculated only in the background (and the main thread provider is generated by posting the
+     * background events in the main handler).
+     */
+    @BindsOptionalOf @UnfoldBgProgressFlag abstract fun unfoldBgProgressFlag(): Boolean
+}
+
 /**
  * Needed as methods inside must be public, but their parameters can be internal (and, a public
  * method can't have internal parameters). Making the module internal and included in a public one
@@ -87,17 +102,34 @@
         fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>,
         foldStateProvider: FoldStateProvider,
         @UnfoldMain mainHandler: Handler,
+        mainThreadUnfoldTransitionProgressProviderFactory:
+            MainThreadUnfoldTransitionProgressProvider.Factory,
+        @UnfoldBg bgProvider: Provider<Optional<UnfoldTransitionProgressProvider>>,
+        @UnfoldBgProgressFlag unfoldBgProgressFlag: Optional<Boolean>,
     ): Optional<UnfoldTransitionProgressProvider> {
-        return createOptionalUnfoldTransitionProgressProvider(
-            config = config,
-            scaleAwareProviderFactory = scaleAwareProviderFactory,
-            tracingListener = tracingListener.create("MainThread"),
-            physicsBasedUnfoldTransitionProgressProvider =
-                physicsBasedUnfoldTransitionProgressProvider,
-            fixedTimingTransitionProgressProvider = fixedTimingTransitionProgressProvider,
-            foldStateProvider = foldStateProvider,
-            progressHandler = mainHandler,
-        )
+        if (unfoldBgProgressFlag.getOrDefault(false)) {
+            // In this case, we wrap the background progress provider
+            val mainThreadProvider: Optional<UnfoldTransitionProgressProvider> =
+                bgProvider.get().map {
+                    mainThreadUnfoldTransitionProgressProviderFactory.create(it)
+                }
+            mainThreadProvider.ifPresent {
+                it.addCallback(tracingListener.create("MainThreadFromBgProgress"))
+            }
+            return mainThreadProvider
+        } else {
+            // TODO(b/277879146): Remove this once unfold_animation_background_progress is launched.
+            return createOptionalUnfoldTransitionProgressProvider(
+                config = config,
+                scaleAwareProviderFactory = scaleAwareProviderFactory,
+                tracingListener = tracingListener.create("MainThread"),
+                physicsBasedUnfoldTransitionProgressProvider =
+                    physicsBasedUnfoldTransitionProgressProvider,
+                fixedTimingTransitionProgressProvider = fixedTimingTransitionProgressProvider,
+                foldStateProvider = foldStateProvider,
+                progressHandler = mainHandler,
+            )
+        }
     }
 
     @Provides
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBgProgressFlag.kt
similarity index 63%
copy from packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
copy to packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBgProgressFlag.kt
index ee36989..0e371fa 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BaseEntry.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBgProgressFlag.kt
@@ -14,16 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.common
+package com.android.systemui.unfold.dagger
 
-import android.app.PendingIntent
-import android.content.Intent
+import javax.inject.Qualifier
 
-open class BaseEntry (
-    val providerId: String,
-    val entryKey: String,
-    val entrySubkey: String,
-    val pendingIntent: PendingIntent?,
-    val fillInIntent: Intent?,
-    val shouldTerminateUiUponSuccessfulProviderResult: Boolean,
-)
\ No newline at end of file
+/**
+ * Annotates the boolean representing whether we are calculating progresses in the background.
+ *
+ * Used to allow clients to provide this value, without depending on the flags directly.
+ */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UnfoldBgProgressFlag
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt
new file mode 100644
index 0000000..9bdf3d5
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold.progress
+
+import android.os.Handler
+import androidx.annotation.FloatRange
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.dagger.UnfoldMain
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/**
+ * [UnfoldTransitionProgressProvider] that forwards all progress to the main thread handler.
+ *
+ * This is needed when progress are calculated in the background, but some listeners need the
+ * callbacks in the main thread.
+ */
+class MainThreadUnfoldTransitionProgressProvider
+@AssistedInject
+constructor(
+    @UnfoldMain private val mainHandler: Handler,
+    @Assisted private val rootProvider: UnfoldTransitionProgressProvider
+) : UnfoldTransitionProgressProvider {
+
+    private val listenerMap = mutableMapOf<TransitionProgressListener, TransitionProgressListener>()
+
+    override fun addCallback(listener: TransitionProgressListener) {
+        assertMainThread()
+        val proxy = TransitionProgressListerProxy(listener)
+        rootProvider.addCallback(proxy)
+        listenerMap[listener] = proxy
+    }
+
+    override fun removeCallback(listener: TransitionProgressListener) {
+        assertMainThread()
+        val proxy = listenerMap.remove(listener) ?: return
+        rootProvider.removeCallback(proxy)
+    }
+
+    private fun assertMainThread() {
+        check(mainHandler.looper.isCurrentThread) {
+            "Should be called from the main thread, but this is ${Thread.currentThread()}"
+        }
+    }
+
+    override fun destroy() {
+        rootProvider.destroy()
+    }
+
+    inner class TransitionProgressListerProxy(private val listener: TransitionProgressListener) :
+        TransitionProgressListener {
+        override fun onTransitionStarted() {
+            mainHandler.post { listener.onTransitionStarted() }
+        }
+
+        override fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {
+            mainHandler.post { listener.onTransitionProgress(progress) }
+        }
+
+        override fun onTransitionFinishing() {
+            mainHandler.post { listener.onTransitionFinishing() }
+        }
+
+        override fun onTransitionFinished() {
+            mainHandler.post { listener.onTransitionFinished() }
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        /** Creates a [MainThreadUnfoldTransitionProgressProvider] that wraps the [rootProvider]. */
+        fun create(
+            rootProvider: UnfoldTransitionProgressProvider
+        ): MainThreadUnfoldTransitionProgressProvider
+    }
+}
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index c70c171..79bfa44 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -1,5 +1,8 @@
 # Ravenwood "policy" file for framework-minus-apex.
 
+# Keep all AIDL interfaces
+class :aidl stubclass
+
 # Collections
 class android.util.ArrayMap stubclass
 class android.util.ArraySet stubclass
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 74538ac..6cac6a4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -668,7 +668,7 @@
             final Context uiContext = displayContext.createWindowContext(
                     TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */);
             magnificationGestureHandler = new WindowMagnificationGestureHandler(uiContext,
-                    mAms.getWindowMagnificationMgr(), mAms.getTraceManager(),
+                    mAms.getMagnificationConnectionManager(), mAms.getTraceManager(),
                     mAms.getMagnificationController(),
                     detectControlGestures,
                     detectTwoFingerTripleTap,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index b5e8c84..2eecb4d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -158,10 +158,10 @@
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.accessibility.magnification.MagnificationConnectionManager;
 import com.android.server.accessibility.magnification.MagnificationController;
 import com.android.server.accessibility.magnification.MagnificationProcessor;
 import com.android.server.accessibility.magnification.MagnificationScaleProvider;
-import com.android.server.accessibility.magnification.WindowMagnificationManager;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
@@ -3442,7 +3442,7 @@
                 && (userState.getMagnificationCapabilitiesLocked()
                 != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
                 || userHasMagnificationServicesLocked(userState);
-        getWindowMagnificationMgr().requestConnection(connect);
+        getMagnificationConnectionManager().requestConnection(connect);
     }
 
     /**
@@ -4122,17 +4122,17 @@
         mSecurityPolicy.enforceCallingOrSelfPermission(
                 android.Manifest.permission.STATUS_BAR_SERVICE);
 
-        getWindowMagnificationMgr().setConnection(connection);
+        getMagnificationConnectionManager().setConnection(connection);
     }
 
     /**
-     * Getter of {@link WindowMagnificationManager}.
+     * Getter of {@link MagnificationConnectionManager}.
      *
-     * @return WindowMagnificationManager
+     * @return MagnificationManager
      */
-    public WindowMagnificationManager getWindowMagnificationMgr() {
+    public MagnificationConnectionManager getMagnificationConnectionManager() {
         synchronized (mLock) {
-            return mMagnificationController.getWindowMagnificationMgr();
+            return mMagnificationController.getMagnificationConnectionManager();
         }
     }
 
@@ -4423,7 +4423,7 @@
                 pw.println();
             }
             pw.append("hasWindowMagnificationConnection=").append(
-                    String.valueOf(getWindowMagnificationMgr().isConnected()));
+                    String.valueOf(getMagnificationConnectionManager().isConnected()));
             pw.println();
             mMagnificationProcessor.dump(pw, getValidDisplayList());
             final int userCount = mUserStates.size();
@@ -5144,7 +5144,7 @@
 
             for (int i = 0; i < displays.size(); i++) {
                 final int displayId = displays.get(i).getDisplayId();
-                getWindowMagnificationMgr().removeMagnificationButton(displayId);
+                getMagnificationConnectionManager().removeMagnificationButton(displayId);
             }
         }
     }
@@ -5580,6 +5580,8 @@
 
     @Override
     public void injectInputEventToInputFilter(InputEvent event) {
+        mSecurityPolicy.enforceCallingPermission(Manifest.permission.INJECT_EVENTS,
+                "injectInputEventToInputFilter");
         synchronized (mLock) {
             final long endMillis =
                     SystemClock.uptimeMillis() + WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index d31b1ef..e3797c9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -258,6 +258,11 @@
                 public void logMagnificationTripleTap(boolean enabled) {
                     AccessibilityStatsLogUtils.logMagnificationTripleTap(enabled);
                 }
+
+                @Override
+                public void logMagnificationTwoFingerTripleTap(boolean enabled) {
+                    AccessibilityStatsLogUtils.logMagnificationTwoFingerTripleTap(enabled);
+                }
             };
         }
 
@@ -419,6 +424,7 @@
     /** An interface that allows testing magnification log events. */
     interface MagnificationLogger {
         void logMagnificationTripleTap(boolean enabled);
+        void logMagnificationTwoFingerTripleTap(boolean enabled);
     }
 
     interface State {
@@ -987,12 +993,14 @@
                             mDisplayId, event.getX(), event.getY())) {
                         transitionToDelegatingStateAndClear();
 
+                    } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) {
+                        // Placing multiple fingers before a single finger, because achieving a
+                        // multi finger multi tap also means achieving a single finger triple tap
+                        onTripleTap(event);
+
                     } else if (isMultiTapTriggered(3 /* taps */)) {
                         onTripleTap(/* up */ event);
 
-                    } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) {
-                        onTripleTap(event);
-
                     } else if (
                             // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
                             isFingerDown()
@@ -1026,6 +1034,11 @@
                 mCompletedTapCount++;
                 mIsTwoFingerCountReached = false;
             }
+
+            if (mDetectTwoFingerTripleTap && mCompletedTapCount > 2) {
+                final boolean enabled = !isActivated();
+                mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
+            }
             return mDetectTwoFingerTripleTap && mCompletedTapCount == targetTapCount;
         }
 
@@ -1037,6 +1050,29 @@
             mFirstPointerDownLocation.set(Float.NaN, Float.NaN);
             mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
         }
+
+        void transitionToViewportDraggingStateAndClear(MotionEvent down) {
+
+            if (DEBUG_DETECTING) Slog.i(mLogTag, "onTripleTapAndHold()");
+            final boolean shortcutTriggered = mShortcutTriggered;
+
+            // Only log the 3tap and hold event
+            if (!shortcutTriggered) {
+                final boolean enabled = !isActivated();
+                if (mCompletedTapCount == 2) {
+                    // Two finger triple tap and hold
+                    mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
+                } else {
+                    // Triple tap and hold also belongs to triple tap event
+                    mMagnificationLogger.logMagnificationTripleTap(enabled);
+                }
+            }
+            clear();
+
+            mViewportDraggingState.prepareForZoomInTemporary(shortcutTriggered);
+            zoomInTemporary(down.getX(), down.getY(), shortcutTriggered);
+            transitionTo(mViewportDraggingState);
+        }
     }
 
     /**
@@ -1416,8 +1452,6 @@
 
             // Only log the 3tap and hold event
             if (!shortcutTriggered) {
-                // TODO:(b/309534286): Add metrics for two-finger triple-tap and fix
-                //  the log two-finger bug before enabling the flag
                 // Triple tap and hold also belongs to triple tap event
                 final boolean enabled = !isActivated();
                 mMagnificationLogger.logMagnificationTripleTap(enabled);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
similarity index 94%
rename from services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
rename to services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
index 3ea805b..5a3c070 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
@@ -60,19 +60,19 @@
 import java.util.concurrent.atomic.AtomicLongFieldUpdater;
 
 /**
- * A class to manipulate window magnification through {@link MagnificationConnectionWrapper}
+ * A class to manipulate magnification through {@link MagnificationConnectionWrapper}
  * create by {@link #setConnection(IWindowMagnificationConnection)}. To set the connection with
  * SysUI, call {@code StatusBarManagerInternal#requestWindowMagnificationConnection(boolean)}.
  * The applied magnification scale is constrained by
  * {@link MagnificationScaleProvider#constrainScale(float)}
  */
-public class WindowMagnificationManager implements
+public class MagnificationConnectionManager implements
         PanningScalingHandler.MagnificationDelegate,
         WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks {
 
     private static final boolean DBG = false;
 
-    private static final String TAG = "WindowMagnificationMgr";
+    private static final String TAG = "MagnificationConnectionManager";
 
     /**
      * Indicate that the magnification window is at the magnification center.
@@ -208,7 +208,7 @@
     private final AccessibilityTraceManager mTrace;
     private final MagnificationScaleProvider mScaleProvider;
 
-    public WindowMagnificationManager(Context context, Object lock, @NonNull Callback callback,
+    public MagnificationConnectionManager(Context context, Object lock, @NonNull Callback callback,
             AccessibilityTraceManager trace, MagnificationScaleProvider scaleProvider) {
         mContext = context;
         mLock = lock;
@@ -1040,7 +1040,7 @@
         private float mScale = MagnificationScaleProvider.MIN_SCALE;
         private boolean mEnabled;
 
-        private final WindowMagnificationManager mWindowMagnificationManager;
+        private final MagnificationConnectionManager mMagnificationConnectionManager;
         // Records the bounds of window magnification.
         private final Rect mBounds = new Rect();
         // The magnified bounds on the screen.
@@ -1058,11 +1058,15 @@
                         "mTrackingTypingFocusSumTime");
         private volatile long mTrackingTypingFocusSumTime = 0;
 
-        WindowMagnifier(int displayId, WindowMagnificationManager windowMagnificationManager) {
+        WindowMagnifier(int displayId,
+                MagnificationConnectionManager magnificationConnectionManager) {
             mDisplayId = displayId;
-            mWindowMagnificationManager = windowMagnificationManager;
+            mMagnificationConnectionManager = magnificationConnectionManager;
         }
 
+        // TODO(b/312324808): Investigating whether
+        //  mMagnificationConnectionManager#enableWindowMagnificationInternal requires a sync lock
+        @SuppressWarnings("GuardedBy")
         boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY,
                 @Nullable MagnificationAnimationCallback animationCallback,
                 @WindowPosition int windowPosition, int id) {
@@ -1072,8 +1076,8 @@
             }
             final float normScale = MagnificationScaleProvider.constrainScale(scale);
             setMagnificationFrameOffsetRatioByWindowPosition(windowPosition);
-            if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale,
-                    centerX, centerY, mMagnificationFrameOffsetRatio.x,
+            if (mMagnificationConnectionManager.enableWindowMagnificationInternal(mDisplayId,
+                    normScale, centerX, centerY, mMagnificationFrameOffsetRatio.x,
                     mMagnificationFrameOffsetRatio.y, animationCallback)) {
                 mScale = normScale;
                 mEnabled = true;
@@ -1096,12 +1100,15 @@
             }
         }
 
+        // TODO(b/312324808): Investigating whether
+        //  mMagnificationConnectionManager#disableWindowMagnificationInternal requires a sync lock
+        @SuppressWarnings("GuardedBy")
         boolean disableWindowMagnificationInternal(
                 @Nullable MagnificationAnimationCallback animationResultCallback) {
             if (!mEnabled) {
                 return false;
             }
-            if (mWindowMagnificationManager.disableWindowMagnificationInternal(
+            if (mMagnificationConnectionManager.disableWindowMagnificationInternal(
                     mDisplayId, animationResultCallback)) {
                 mEnabled = false;
                 mIdOfLastServiceToControl = INVALID_SERVICE_ID;
@@ -1112,6 +1119,10 @@
             return false;
         }
 
+        // ErrorProne says the access of mMagnificationConnectionManager#setScaleInternal should
+        // be guarded by 'this.mMagnificationConnectionManager.mLock' which is the same one as
+        // 'mLock'. Therefore, we'll put @SuppressWarnings here.
+        @SuppressWarnings("GuardedBy")
         @GuardedBy("mLock")
         void setScale(float scale) {
             if (!mEnabled) {
@@ -1119,7 +1130,8 @@
             }
             final float normScale = MagnificationScaleProvider.constrainScale(scale);
             if (Float.compare(mScale, normScale) != 0
-                    && mWindowMagnificationManager.setScaleInternal(mDisplayId, scale)) {
+                    && mMagnificationConnectionManager
+                        .setScaleForWindowMagnificationInternal(mDisplayId, scale)) {
                 mScale = normScale;
             }
         }
@@ -1159,8 +1171,8 @@
         }
 
         void setTrackingTypingFocusEnabled(boolean trackingTypingFocusEnabled) {
-            if (mWindowMagnificationManager.isWindowMagnifierEnabled(mDisplayId)
-                    && mWindowMagnificationManager.isImeVisible(mDisplayId)
+            if (mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId)
+                    && mMagnificationConnectionManager.isImeVisible(mDisplayId)
                     && trackingTypingFocusEnabled) {
                 startTrackingTypingFocusRecord();
             }
@@ -1206,7 +1218,7 @@
                     Slog.d(TAG, "stop and log: session duration = " + duration
                             + ", elapsed = " + elapsed);
                 }
-                mWindowMagnificationManager.logTrackingTypingFocus(duration);
+                mMagnificationConnectionManager.logTrackingTypingFocus(duration);
                 mTrackingTypingFocusStartTime = 0;
                 mTrackingTypingFocusSumTime = 0;
             }
@@ -1216,9 +1228,14 @@
             return mEnabled;
         }
 
+        // ErrorProne says the access of mMagnificationConnectionManager#moveWindowMagnifierInternal
+        // should be guarded by 'this.mMagnificationConnectionManager.mLock' which is the same one
+        // as 'mLock'. Therefore, we'll put @SuppressWarnings here.
+        @SuppressWarnings("GuardedBy")
         @GuardedBy("mLock")
         void move(float offsetX, float offsetY) {
-            mWindowMagnificationManager.moveWindowMagnifierInternal(mDisplayId, offsetX, offsetY);
+            mMagnificationConnectionManager.moveWindowMagnifierInternal(
+                    mDisplayId, offsetX, offsetY);
         }
 
         @GuardedBy("mLock")
@@ -1270,8 +1287,10 @@
                 animationCallback);
     }
 
-    private boolean setScaleInternal(int displayId, float scale) {
-        return mConnectionWrapper != null && mConnectionWrapper.setScale(displayId, scale);
+    @GuardedBy("mLock")
+    private boolean setScaleForWindowMagnificationInternal(int displayId, float scale) {
+        return mConnectionWrapper != null
+                && mConnectionWrapper.setScaleForWindowMagnification(displayId, scale);
     }
 
     @GuardedBy("mLock")
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
index f0c44d6..20538f1 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
@@ -82,16 +82,16 @@
         return true;
     }
 
-    boolean setScale(int displayId, float scale) {
+    boolean setScaleForWindowMagnification(int displayId, float scale) {
         if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
             mTrace.logTrace(TAG + ".setScale", FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
                     "displayId=" + displayId + ";scale=" + scale);
         }
         try {
-            mConnection.setScale(displayId, scale);
+            mConnection.setScaleForWindowMagnification(displayId, scale);
         } catch (RemoteException e) {
             if (DBG) {
-                Slog.e(TAG, "Error calling setScale()", e);
+                Slog.e(TAG, "Error calling setScaleForWindowMagnification()", e);
             }
             return false;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index effd873..52e123a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -77,7 +77,7 @@
  *  <b>Note</b> Updates magnification switch UI when magnification mode transition
  *  is done and before invoking {@link TransitionCallBack#onResult}.
  */
-public class MagnificationController implements WindowMagnificationManager.Callback,
+public class MagnificationController implements MagnificationConnectionManager.Callback,
         MagnificationGestureHandler.Callback,
         FullScreenMagnificationController.MagnificationInfoChangedCallback,
         WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks {
@@ -96,7 +96,7 @@
     private final AlwaysOnMagnificationFeatureFlag mAlwaysOnMagnificationFeatureFlag;
     private final MagnificationScaleProvider mScaleProvider;
     private FullScreenMagnificationController mFullScreenMagnificationController;
-    private WindowMagnificationManager mWindowMagnificationMgr;
+    private MagnificationConnectionManager mMagnificationConnectionManager;
     private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
     /** Whether the platform supports window magnification feature. */
     private final boolean mSupportWindowMagnification;
@@ -164,11 +164,11 @@
     @VisibleForTesting
     public MagnificationController(AccessibilityManagerService ams, Object lock,
             Context context, FullScreenMagnificationController fullScreenMagnificationController,
-            WindowMagnificationManager windowMagnificationManager,
+            MagnificationConnectionManager magnificationConnectionManager,
             MagnificationScaleProvider scaleProvider, Executor backgroundExecutor) {
         this(ams, lock, context, scaleProvider, backgroundExecutor);
         mFullScreenMagnificationController = fullScreenMagnificationController;
-        mWindowMagnificationMgr = windowMagnificationManager;
+        mMagnificationConnectionManager = magnificationConnectionManager;
     }
 
     @Override
@@ -179,10 +179,10 @@
             if (updatePersistence) {
                 getFullScreenMagnificationController().persistScale(displayId);
             }
-        } else if (getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId)) {
-            getWindowMagnificationMgr().setScale(displayId, scale);
+        } else if (getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId)) {
+            getMagnificationConnectionManager().setScale(displayId, scale);
             if (updatePersistence) {
-                getWindowMagnificationMgr().persistScale(displayId);
+                getMagnificationConnectionManager().persistScale(displayId);
             }
         }
     }
@@ -222,15 +222,15 @@
         }
 
         if (showModeSwitchButton) {
-            getWindowMagnificationMgr().showMagnificationButton(displayId, mode);
+            getMagnificationConnectionManager().showMagnificationButton(displayId, mode);
         } else {
-            getWindowMagnificationMgr().removeMagnificationButton(displayId);
+            getMagnificationConnectionManager().removeMagnificationButton(displayId);
         }
 
         if (!enableSettingsPanel) {
             // Whether the settings panel needs to be shown is controlled in system UI.
             // Here, we only guarantee that the settings panel is closed when it is not needed.
-            getWindowMagnificationMgr().removeMagnificationSettingsPanel(displayId);
+            getMagnificationConnectionManager().removeMagnificationSettingsPanel(displayId);
         }
     }
 
@@ -284,7 +284,8 @@
 
         final FullScreenMagnificationController screenMagnificationController =
                 getFullScreenMagnificationController();
-        final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
+        final MagnificationConnectionManager magnificationConnectionManager =
+                getMagnificationConnectionManager();
         final float scale = getTargetModeScaleFromCurrentMagnification(displayId, targetMode);
         final DisableMagnificationCallback animationEndCallback =
                 new DisableMagnificationCallback(transitionCallBack, displayId, targetMode,
@@ -295,7 +296,7 @@
         if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
             screenMagnificationController.reset(displayId, animationEndCallback);
         } else {
-            windowMagnificationMgr.disableWindowMagnification(displayId, false,
+            magnificationConnectionManager.disableWindowMagnification(displayId, false,
                     animationEndCallback);
         }
     }
@@ -340,7 +341,8 @@
             }
             final FullScreenMagnificationController screenMagnificationController =
                     getFullScreenMagnificationController();
-            final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
+            final MagnificationConnectionManager magnificationConnectionManager =
+                    getMagnificationConnectionManager();
             final float targetScale = Float.isNaN(config.getScale())
                     ? getTargetModeScaleFromCurrentMagnification(displayId, targetMode)
                     : config.getScale();
@@ -353,14 +355,15 @@
                 if (targetMode == MAGNIFICATION_MODE_WINDOW) {
                     screenMagnificationController.reset(displayId, false);
                     if (targetActivated) {
-                        windowMagnificationMgr.enableWindowMagnification(displayId,
+                        magnificationConnectionManager.enableWindowMagnification(displayId,
                                 targetScale, magnificationCenter.x, magnificationCenter.y,
                                 magnificationAnimationCallback, id);
                     } else {
-                        windowMagnificationMgr.disableWindowMagnification(displayId, false);
+                        magnificationConnectionManager.disableWindowMagnification(displayId, false);
                     }
                 } else if (targetMode == MAGNIFICATION_MODE_FULLSCREEN) {
-                    windowMagnificationMgr.disableWindowMagnification(displayId, false, null);
+                    magnificationConnectionManager.disableWindowMagnification(
+                            displayId, false, null);
                     if (targetActivated) {
                         if (!screenMagnificationController.isRegistered(displayId)) {
                             screenMagnificationController.register(displayId);
@@ -409,7 +412,7 @@
         if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
             return getFullScreenMagnificationController().getScale(displayId);
         } else {
-            return getWindowMagnificationMgr().getScale(displayId);
+            return getMagnificationConnectionManager().getScale(displayId);
         }
     }
 
@@ -441,7 +444,8 @@
             mAccessibilityCallbacksDelegateArray.put(displayId,
                     getFullScreenMagnificationController());
         } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
-            mAccessibilityCallbacksDelegateArray.put(displayId, getWindowMagnificationMgr());
+            mAccessibilityCallbacksDelegateArray.put(
+                    displayId, getMagnificationConnectionManager());
         } else {
             mAccessibilityCallbacksDelegateArray.delete(displayId);
         }
@@ -462,13 +466,13 @@
 
     @Override
     public void onRequestMagnificationSpec(int displayId, int serviceId) {
-        final WindowMagnificationManager windowMagnificationManager;
+        final MagnificationConnectionManager magnificationConnectionManager;
         synchronized (mLock) {
             updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
-            windowMagnificationManager = mWindowMagnificationMgr;
+            magnificationConnectionManager = mMagnificationConnectionManager;
         }
-        if (windowMagnificationManager != null) {
-            mWindowMagnificationMgr.disableWindowMagnification(displayId, false);
+        if (magnificationConnectionManager != null) {
+            mMagnificationConnectionManager.disableWindowMagnification(displayId, false);
         }
     }
 
@@ -491,7 +495,7 @@
                 setCurrentMagnificationModeAndSwitchDelegate(displayId,
                         ACCESSIBILITY_MAGNIFICATION_MODE_NONE);
                 duration = SystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId);
-                scale = mWindowMagnificationMgr.getLastActivatedScale(displayId);
+                scale = mMagnificationConnectionManager.getLastActivatedScale(displayId);
             }
             logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration, scale);
         }
@@ -507,13 +511,14 @@
     public void onSourceBoundsChanged(int displayId, Rect bounds) {
         if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) {
             // notify sysui the magnification scale changed on window magnifier
-            mWindowMagnificationMgr.onUserMagnificationScaleChanged(
-                    mUserId, displayId, getWindowMagnificationMgr().getScale(displayId));
+            mMagnificationConnectionManager.onUserMagnificationScaleChanged(
+                    mUserId, displayId, getMagnificationConnectionManager().getScale(displayId));
 
             final MagnificationConfig config = new MagnificationConfig.Builder()
                     .setMode(MAGNIFICATION_MODE_WINDOW)
-                    .setActivated(getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId))
-                    .setScale(getWindowMagnificationMgr().getScale(displayId))
+                    .setActivated(
+                            getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId))
+                    .setScale(getMagnificationConnectionManager().getScale(displayId))
                     .setCenterX(bounds.exactCenterX())
                     .setCenterY(bounds.exactCenterY()).build();
             mAms.notifyMagnificationChanged(displayId, new Region(bounds), config);
@@ -525,7 +530,7 @@
             @NonNull MagnificationConfig config) {
         if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) {
             // notify sysui the magnification scale changed on fullscreen magnifier
-            mWindowMagnificationMgr.onUserMagnificationScaleChanged(
+            mMagnificationConnectionManager.onUserMagnificationScaleChanged(
                     mUserId, displayId, config.getScale());
 
             mAms.notifyMagnificationChanged(displayId, region, config);
@@ -548,8 +553,8 @@
         synchronized (mLock) {
             final boolean fullScreenActivated = mFullScreenMagnificationController != null
                     && mFullScreenMagnificationController.isActivated(displayId);
-            final boolean windowEnabled = mWindowMagnificationMgr != null
-                    && mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId);
+            final boolean windowEnabled = mMagnificationConnectionManager != null
+                    && mMagnificationConnectionManager.isWindowMagnifierEnabled(displayId);
             final Integer transitionMode = mTransitionModes.get(displayId);
             if (((changeMode == MAGNIFICATION_MODE_FULLSCREEN && fullScreenActivated)
                     || (changeMode == MAGNIFICATION_MODE_WINDOW && windowEnabled))
@@ -608,10 +613,10 @@
     }
 
     private void disableWindowMagnificationIfNeeded(int displayId) {
-        final WindowMagnificationManager windowMagnificationManager =
-                getWindowMagnificationMgr();
+        final MagnificationConnectionManager magnificationConnectionManager =
+                getMagnificationConnectionManager();
         if (isActivated(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) {
-            windowMagnificationManager.disableWindowMagnification(displayId, false);
+            magnificationConnectionManager.disableWindowMagnification(displayId, false);
         }
     }
 
@@ -620,7 +625,7 @@
         synchronized (mLock) {
             mIsImeVisibleArray.put(displayId, shown);
         }
-        getWindowMagnificationMgr().onImeWindowVisibilityChanged(displayId, shown);
+        getMagnificationConnectionManager().onImeWindowVisibilityChanged(displayId, shown);
         logMagnificationModeWithImeOnIfNeeded(displayId);
     }
 
@@ -661,7 +666,7 @@
 
     /**
      * Updates the active user ID of {@link FullScreenMagnificationController} and {@link
-     * WindowMagnificationManager}.
+     * MagnificationConnectionManager}.
      *
      * @param userId the currently active user ID
      */
@@ -671,10 +676,10 @@
         }
         mUserId = userId;
         final FullScreenMagnificationController fullMagnificationController;
-        final WindowMagnificationManager windowMagnificationManager;
+        final MagnificationConnectionManager magnificationConnectionManager;
         synchronized (mLock) {
             fullMagnificationController = mFullScreenMagnificationController;
-            windowMagnificationManager = mWindowMagnificationMgr;
+            magnificationConnectionManager = mMagnificationConnectionManager;
             mAccessibilityCallbacksDelegateArray.clear();
             mCurrentMagnificationModeArray.clear();
             mLastMagnificationActivatedModeArray.clear();
@@ -685,8 +690,8 @@
         if (fullMagnificationController != null) {
             fullMagnificationController.resetAllIfNeeded(false);
         }
-        if (windowMagnificationManager != null) {
-            windowMagnificationManager.disableAllWindowMagnifiers();
+        if (magnificationConnectionManager != null) {
+            magnificationConnectionManager.disableAllWindowMagnifiers();
         }
     }
 
@@ -700,8 +705,8 @@
             if (mFullScreenMagnificationController != null) {
                 mFullScreenMagnificationController.onDisplayRemoved(displayId);
             }
-            if (mWindowMagnificationMgr != null) {
-                mWindowMagnificationMgr.onDisplayRemoved(displayId);
+            if (mMagnificationConnectionManager != null) {
+                mMagnificationConnectionManager.onDisplayRemoved(displayId);
             }
             mAccessibilityCallbacksDelegateArray.delete(displayId);
             mCurrentMagnificationModeArray.delete(displayId);
@@ -728,7 +733,7 @@
      * @param enabled Enable the following typing focus feature
      */
     public void setMagnificationFollowTypingEnabled(boolean enabled) {
-        getWindowMagnificationMgr().setMagnificationFollowTypingEnabled(enabled);
+        getMagnificationConnectionManager().setMagnificationFollowTypingEnabled(enabled);
         getFullScreenMagnificationController().setMagnificationFollowTypingEnabled(enabled);
     }
 
@@ -805,29 +810,29 @@
     }
 
     /**
-     * Getter of {@link WindowMagnificationManager}.
+     * Getter of {@link MagnificationConnectionManager}.
      *
-     * @return {@link WindowMagnificationManager}.
+     * @return {@link MagnificationConnectionManager}.
      */
-    public WindowMagnificationManager getWindowMagnificationMgr() {
+    public MagnificationConnectionManager getMagnificationConnectionManager() {
         synchronized (mLock) {
-            if (mWindowMagnificationMgr == null) {
-                mWindowMagnificationMgr = new WindowMagnificationManager(mContext,
+            if (mMagnificationConnectionManager == null) {
+                mMagnificationConnectionManager = new MagnificationConnectionManager(mContext,
                         mLock, this, mAms.getTraceManager(),
                         mScaleProvider);
             }
-            return mWindowMagnificationMgr;
+            return mMagnificationConnectionManager;
         }
     }
 
     private @Nullable PointF getCurrentMagnificationCenterLocked(int displayId, int targetMode) {
         if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
-            if (mWindowMagnificationMgr == null
-                    || !mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId)) {
+            if (mMagnificationConnectionManager == null
+                    || !mMagnificationConnectionManager.isWindowMagnifierEnabled(displayId)) {
                 return null;
             }
-            mTempPoint.set(mWindowMagnificationMgr.getCenterX(displayId),
-                    mWindowMagnificationMgr.getCenterY(displayId));
+            mTempPoint.set(mMagnificationConnectionManager.getCenterX(displayId),
+                    mMagnificationConnectionManager.getCenterY(displayId));
         } else {
             if (mFullScreenMagnificationController == null
                     || !mFullScreenMagnificationController.isActivated(displayId)) {
@@ -858,10 +863,10 @@
             }
         } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
             synchronized (mLock) {
-                if (mWindowMagnificationMgr == null) {
+                if (mMagnificationConnectionManager == null) {
                     return false;
                 }
-                isActivated = mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId);
+                isActivated = mMagnificationConnectionManager.isWindowMagnifierEnabled(displayId);
             }
         }
         return isActivated;
@@ -980,7 +985,7 @@
                         mCurrentCenter.x, mCurrentCenter.y, mAnimate,
                         MAGNIFICATION_GESTURE_HANDLER_ID);
             } else {
-                getWindowMagnificationMgr().enableWindowMagnification(mDisplayId,
+                getMagnificationConnectionManager().enableWindowMagnification(mDisplayId,
                         mCurrentScale, mCurrentCenter.x,
                         mCurrentCenter.y, mAnimate ? STUB_ANIMATION_CALLBACK : null,
                         MAGNIFICATION_GESTURE_HANDLER_ID);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index 5cf2a63..ed8f1ab 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -84,13 +84,13 @@
                     .setCenterX(fullScreenMagnificationController.getCenterX(displayId))
                     .setCenterY(fullScreenMagnificationController.getCenterY(displayId));
         } else if (mode == MAGNIFICATION_MODE_WINDOW) {
-            final WindowMagnificationManager windowMagnificationManager =
-                    mController.getWindowMagnificationMgr();
+            final MagnificationConnectionManager magnificationConnectionManager =
+                    mController.getMagnificationConnectionManager();
             builder.setMode(mode)
                     .setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_WINDOW))
-                    .setScale(windowMagnificationManager.getScale(displayId))
-                    .setCenterX(windowMagnificationManager.getCenterX(displayId))
-                    .setCenterY(windowMagnificationManager.getCenterY(displayId));
+                    .setScale(magnificationConnectionManager.getScale(displayId))
+                    .setCenterX(magnificationConnectionManager.getCenterX(displayId))
+                    .setCenterY(magnificationConnectionManager.getCenterY(displayId));
         } else {
             // For undefined mode, set enabled to false
             builder.setActivated(false);
@@ -135,12 +135,12 @@
             }
         } else if (configMode == MAGNIFICATION_MODE_WINDOW) {
             if (configActivated) {
-                return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId,
-                        config.getScale(), config.getCenterX(), config.getCenterY(),
+                return mController.getMagnificationConnectionManager().enableWindowMagnification(
+                        displayId, config.getScale(), config.getCenterX(), config.getCenterY(),
                         animate ? STUB_ANIMATION_CALLBACK : null,
                         id);
             } else {
-                return mController.getWindowMagnificationMgr()
+                return mController.getMagnificationConnectionManager()
                         .disableWindowMagnification(displayId, false);
             }
         }
@@ -256,7 +256,7 @@
         if (currentMode == MAGNIFICATION_MODE_FULLSCREEN) {
             getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification);
         } else if (currentMode == MAGNIFICATION_MODE_WINDOW) {
-            mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId,
+            mController.getMagnificationConnectionManager().getMagnificationSourceBounds(displayId,
                     outRegion);
         }
     }
@@ -297,8 +297,8 @@
         if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
             return mController.getFullScreenMagnificationController().reset(displayId, animate);
         } else if (mode == MAGNIFICATION_MODE_WINDOW) {
-            return mController.getWindowMagnificationMgr().disableWindowMagnification(displayId,
-                    false, animate ? STUB_ANIMATION_CALLBACK : null);
+            return mController.getMagnificationConnectionManager().disableWindowMagnification(
+                    displayId, false, animate ? STUB_ANIMATION_CALLBACK : null);
         }
         return false;
     }
@@ -325,19 +325,20 @@
      */
     public void resetAllIfNeeded(int connectionId) {
         mController.getFullScreenMagnificationController().resetAllIfNeeded(connectionId);
-        mController.getWindowMagnificationMgr().resetAllIfNeeded(connectionId);
+        mController.getMagnificationConnectionManager().resetAllIfNeeded(connectionId);
     }
 
     /**
      * {@link FullScreenMagnificationController#isActivated(int)}
-     * {@link WindowMagnificationManager#isWindowMagnifierEnabled(int)}
+     * {@link MagnificationConnectionManager#isWindowMagnifierEnabled(int)}
      */
     public boolean isMagnifying(int displayId) {
         int mode = getControllingMode(displayId);
         if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
             return mController.getFullScreenMagnificationController().isActivated(displayId);
         } else if (mode == MAGNIFICATION_MODE_WINDOW) {
-            return mController.getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId);
+            return mController.getMagnificationConnectionManager().isWindowMagnifierEnabled(
+                    displayId);
         }
         return false;
     }
@@ -416,22 +417,23 @@
         pw.append("    SupportWindowMagnification="
                 + mController.supportWindowMagnification()).println();
         pw.append("    WindowMagnificationConnectionState="
-                + mController.getWindowMagnificationMgr().getConnectionState()).println();
+                + mController.getMagnificationConnectionManager().getConnectionState()).println();
     }
 
     private int getIdOfLastServiceToMagnify(int mode, int displayId) {
         return (mode == MAGNIFICATION_MODE_FULLSCREEN)
                 ? mController.getFullScreenMagnificationController()
                 .getIdOfLastServiceToMagnify(displayId)
-                : mController.getWindowMagnificationMgr().getIdOfLastServiceToMagnify(
+                : mController.getMagnificationConnectionManager().getIdOfLastServiceToMagnify(
                         displayId);
     }
 
     private void dumpTrackingTypingFocusEnabledState(final PrintWriter pw, int displayId,
             int mode) {
         if (mode == MAGNIFICATION_MODE_WINDOW) {
-            pw.append("    TrackingTypingFocusEnabled="  + mController
-                            .getWindowMagnificationMgr().isTrackingTypingFocusEnabled(displayId))
+            pw.append("    TrackingTypingFocusEnabled="
+                            + mController.getMagnificationConnectionManager()
+                                .isTrackingTypingFocusEnabled(displayId))
                     .println();
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
index 9455628..6b48d2b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
@@ -45,6 +45,7 @@
     private static final String TAG = "PanningScalingHandler";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    // TODO(b/312372035): Revisit the scope of usage of the interface
     interface MagnificationDelegate {
         boolean processScroll(int displayId, float distanceX, float distanceY);
         void setScale(int displayId, float scale);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 36e75118..73c267a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -79,7 +79,7 @@
     private static final float MIN_SCALE = 1.0f;
     private static final float MAX_SCALE = MagnificationScaleProvider.MAX_SCALE;
 
-    private final WindowMagnificationManager mWindowMagnificationMgr;
+    private final MagnificationConnectionManager mMagnificationConnectionManager;
     @VisibleForTesting
     final DelegatingState mDelegatingState;
     @VisibleForTesting
@@ -101,7 +101,7 @@
     private long mTripleTapAndHoldStartedTime = 0;
 
     public WindowMagnificationGestureHandler(@UiContext Context context,
-            WindowMagnificationManager windowMagnificationMgr,
+            MagnificationConnectionManager magnificationConnectionManager,
             AccessibilityTraceManager trace,
             Callback callback,
             boolean detectSingleFingerTripleTap,
@@ -115,7 +115,7 @@
                     "WindowMagnificationGestureHandler() , displayId = " + displayId + ")");
         }
         mContext = context;
-        mWindowMagnificationMgr = windowMagnificationMgr;
+        mMagnificationConnectionManager = magnificationConnectionManager;
         mMotionEventDispatcherDelegate = new MotionEventDispatcherDelegate(context,
                 (event, rawEvent, policyFlags) -> dispatchTransformedEvent(event, rawEvent,
                         policyFlags));
@@ -128,18 +128,18 @@
                             @Override
                             public boolean processScroll(int displayId, float distanceX,
                                     float distanceY) {
-                                return mWindowMagnificationMgr.processScroll(displayId, distanceX,
-                                        distanceY);
+                                return mMagnificationConnectionManager.processScroll(
+                                        displayId, distanceX, distanceY);
                             }
 
                             @Override
                             public void setScale(int displayId, float scale) {
-                                mWindowMagnificationMgr.setScale(displayId, scale);
+                                mMagnificationConnectionManager.setScale(displayId, scale);
                             }
 
                             @Override
                             public float getScale(int displayId) {
-                                return mWindowMagnificationMgr.getScale(displayId);
+                                return mMagnificationConnectionManager.getScale(displayId);
                             }
                         }));
 
@@ -167,7 +167,7 @@
             Slog.i(mLogTag, "onDestroy(); delayed = "
                     + mDetectingState.toString());
         }
-        mWindowMagnificationMgr.disableWindowMagnification(mDisplayId, true);
+        mMagnificationConnectionManager.disableWindowMagnification(mDisplayId, true);
         resetToDetectState();
     }
 
@@ -176,7 +176,7 @@
         final Point screenSize = mTempPoint;
         getScreenSize(mTempPoint);
         toggleMagnification(screenSize.x / 2.0f, screenSize.y / 2.0f,
-                WindowMagnificationManager.WINDOW_POSITION_AT_CENTER);
+                MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER);
     }
 
     private  void getScreenSize(Point outSize) {
@@ -190,28 +190,29 @@
     }
 
     private void enableWindowMagnifier(float centerX, float centerY,
-            @WindowMagnificationManager.WindowPosition int windowPosition) {
+            @MagnificationConnectionManager.WindowPosition int windowPosition) {
         if (DEBUG_ALL) {
             Slog.i(mLogTag, "enableWindowMagnifier :"
                     + centerX + ", " + centerY + ", " + windowPosition);
         }
 
         final float scale = MathUtils.constrain(
-                mWindowMagnificationMgr.getPersistedScale(mDisplayId), MIN_SCALE, MAX_SCALE);
-        mWindowMagnificationMgr.enableWindowMagnification(mDisplayId, scale, centerX, centerY,
-                windowPosition);
+                mMagnificationConnectionManager.getPersistedScale(mDisplayId),
+                MIN_SCALE, MAX_SCALE);
+        mMagnificationConnectionManager.enableWindowMagnification(
+                mDisplayId, scale, centerX, centerY, windowPosition);
     }
 
     private void disableWindowMagnifier() {
         if (DEBUG_ALL) {
             Slog.i(mLogTag, "disableWindowMagnifier()");
         }
-        mWindowMagnificationMgr.disableWindowMagnification(mDisplayId, false);
+        mMagnificationConnectionManager.disableWindowMagnification(mDisplayId, false);
     }
 
     private void toggleMagnification(float centerX, float centerY,
-            @WindowMagnificationManager.WindowPosition int windowPosition) {
-        if (mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId)) {
+            @MagnificationConnectionManager.WindowPosition int windowPosition) {
+        if (mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId)) {
             disableWindowMagnifier();
         } else {
             enableWindowMagnifier(centerX, centerY, windowPosition);
@@ -223,7 +224,7 @@
             Slog.i(mLogTag, "onTripleTap()");
         }
         toggleMagnification(up.getX(), up.getY(),
-                WindowMagnificationManager.WINDOW_POSITION_AT_CENTER);
+                MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER);
     }
 
     @VisibleForTesting
@@ -232,9 +233,9 @@
             Slog.i(mLogTag, "onTripleTapAndHold()");
         }
         mViewportDraggingState.mEnabledBeforeDrag =
-                mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId);
+                mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId);
         enableWindowMagnifier(up.getX(), up.getY(),
-                WindowMagnificationManager.WINDOW_POSITION_AT_TOP_LEFT);
+                MagnificationConnectionManager.WINDOW_POSITION_AT_TOP_LEFT);
         mTripleTapAndHoldStartedTime = SystemClock.uptimeMillis();
         transitionTo(mViewportDraggingState);
     }
@@ -242,7 +243,7 @@
     @VisibleForTesting
     void releaseTripleTapAndHold() {
         if (!mViewportDraggingState.mEnabledBeforeDrag) {
-            mWindowMagnificationMgr.disableWindowMagnification(mDisplayId, true);
+            mMagnificationConnectionManager.disableWindowMagnification(mDisplayId, true);
         }
         transitionTo(mDetectingState);
         if (mTripleTapAndHoldStartedTime != 0) {
@@ -331,7 +332,7 @@
         @Override
         public void onExit() {
             mPanningScalingHandler.setEnabled(false);
-            mWindowMagnificationMgr.persistScale(mDisplayId);
+            mMagnificationConnectionManager.persistScale(mDisplayId);
             clear();
         }
 
@@ -404,7 +405,7 @@
                     if (!Float.isNaN(mLastX) && !Float.isNaN(mLastY)) {
                         float offsetX = event.getX() - mLastX;
                         float offsetY = event.getY() - mLastY;
-                        mWindowMagnificationMgr.moveWindowMagnification(mDisplayId, offsetX,
+                        mMagnificationConnectionManager.moveWindowMagnification(mDisplayId, offsetX,
                                 offsetY);
                     }
                     mLastX = event.getX();
@@ -522,7 +523,7 @@
 
         @Override
         public boolean shouldStopDetection(MotionEvent motionEvent) {
-            return !mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId)
+            return !mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId)
                     && !mDetectSingleFingerTripleTap
                     && !(mDetectTwoFingerTripleTap
                     && Flags.enableMagnificationMultipleFingerMultipleTapGesture());
@@ -540,7 +541,8 @@
                         "onGestureDetected : delayedEventQueue = " + delayedEventQueue);
             }
             if (gestureId == MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE
-                    && mWindowMagnificationMgr.pointersInWindow(mDisplayId, motionEvent) > 0) {
+                    && mMagnificationConnectionManager
+                        .pointersInWindow(mDisplayId, motionEvent) > 0) {
                 transitionTo(mObservePanningScalingState);
             } else if (gestureId == MagnificationGestureMatcher.GESTURE_TRIPLE_TAP) {
                 onTripleTap(motionEvent);
@@ -584,7 +586,7 @@
                 + ", mMagnifiedInteractionState=" + mObservePanningScalingState
                 + ", mCurrentState=" + State.nameOf(mCurrentState)
                 + ", mPreviousState=" + State.nameOf(mPreviousState)
-                + ", mWindowMagnificationMgr=" + mWindowMagnificationMgr
+                + ", mMagnificationConnectionManager=" + mMagnificationConnectionManager
                 + ", mDisplayId=" + mDisplayId
                 + '}';
     }
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 3583a78..a159a5e 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -55,5 +55,15 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsVirtualDevicesCameraTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
   ]
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 4987fbc..45d7314 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -939,7 +939,7 @@
         if (mVirtualCameraController == null) {
             throw new UnsupportedOperationException("Virtual camera controller is not available");
         }
-        mVirtualCameraController.registerCamera(Objects.requireNonNull(cameraConfig));
+        mVirtualCameraController.registerCamera(cameraConfig);
     }
 
     @Override // Binder call
@@ -951,7 +951,19 @@
         if (mVirtualCameraController == null) {
             throw new UnsupportedOperationException("Virtual camera controller is not available");
         }
-        mVirtualCameraController.unregisterCamera(Objects.requireNonNull(cameraConfig));
+        mVirtualCameraController.unregisterCamera(cameraConfig);
+    }
+
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public int getVirtualCameraId(@NonNull VirtualCameraConfig cameraConfig)
+            throws RemoteException {
+        super.getVirtualCameraId_enforcePermission();
+        Objects.requireNonNull(cameraConfig);
+        if (mVirtualCameraController == null) {
+            throw new UnsupportedOperationException("Virtual camera controller is not available");
+        }
+        return mVirtualCameraController.getCameraId(cameraConfig);
     }
 
     @Override
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index d194bb2..9b78ed4 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -460,11 +460,11 @@
 
             synchronized (mVirtualDeviceManagerLock) {
                 if (!Flags.persistentDeviceIdApi() && mVirtualDevices.size() == 0) {
-                    final long callindId = Binder.clearCallingIdentity();
+                    final long callingId = Binder.clearCallingIdentity();
                     try {
                         registerCdmAssociationListener();
                     } finally {
-                        Binder.restoreCallingIdentity(callindId);
+                        Binder.restoreCallingIdentity(callingId);
                     }
                 }
                 mVirtualDevices.put(deviceId, virtualDevice);
@@ -498,13 +498,16 @@
             synchronized (mVirtualDeviceManagerLock) {
                 virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getDeviceId());
                 if (virtualDeviceImpl == null) {
-                    throw new SecurityException("Invalid VirtualDevice");
+                    throw new SecurityException(
+                            "Invalid VirtualDevice (deviceId = " + virtualDevice.getDeviceId()
+                                    + ")");
                 }
             }
             if (virtualDeviceImpl.getOwnerUid() != callingUid) {
                 throw new SecurityException(
                         "uid " + callingUid
-                                + " is not the owner of the supplied VirtualDevice");
+                                + " is not the owner of the supplied VirtualDevice (deviceId = "
+                                + virtualDevice.getDeviceId() + ")");
             }
 
             return virtualDeviceImpl.createVirtualDisplay(
@@ -851,6 +854,11 @@
         }
 
         @Override
+        public int getDeviceIdForDisplayId(int displayId) {
+            return mImpl.getDeviceIdForDisplayId(displayId);
+        }
+
+        @Override
         public @Nullable String getPersistentIdForDevice(int deviceId) {
             if (deviceId == Context.DEVICE_ID_DEFAULT) {
                 return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 06be3f3..d089b05 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -27,14 +27,14 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.util.ArraySet;
+import android.util.ArrayMap;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
-import java.util.Set;
+import java.util.Map;
 
 /**
  * Manages the registration and removal of virtual camera from the server side.
@@ -47,10 +47,13 @@
     private static final String VIRTUAL_CAMERA_SERVICE_NAME = "virtual_camera";
     private static final String TAG = "VirtualCameraController";
 
+    private final Object mServiceLock = new Object();
+
+    @GuardedBy("mServiceLock")
     @Nullable private IVirtualCameraService mVirtualCameraService;
 
     @GuardedBy("mCameras")
-    private final Set<VirtualCameraConfig> mCameras = new ArraySet<>();
+    private final Map<IBinder, CameraDescriptor> mCameras = new ArrayMap<>();
 
     public VirtualCameraController() {
         connectVirtualCameraService();
@@ -67,19 +70,16 @@
      * @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
      */
     public void registerCamera(@NonNull VirtualCameraConfig cameraConfig) {
-        // Try to connect to service if not connected already.
-        if (mVirtualCameraService == null) {
-            connectVirtualCameraService();
-        }
-        // Throw exception if we are unable to connect to service.
-        if (mVirtualCameraService == null) {
-            throw new IllegalStateException("Virtual camera service is not connected.");
-        }
+        connectVirtualCameraServiceIfNeeded();
 
         try {
             if (registerCameraWithService(cameraConfig)) {
+                CameraDescriptor cameraDescriptor =
+                        new CameraDescriptor(cameraConfig);
+                IBinder binder = cameraConfig.getCallback().asBinder();
+                binder.linkToDeath(cameraDescriptor, 0 /* flags */);
                 synchronized (mCameras) {
-                    mCameras.add(cameraConfig);
+                    mCameras.put(binder, cameraDescriptor);
                 }
             } else {
                 // TODO(b/310857519): Revisit this to find a better way of indicating failure.
@@ -96,24 +96,44 @@
      * @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
      */
     public void unregisterCamera(@NonNull VirtualCameraConfig cameraConfig) {
-        try {
-            if (mVirtualCameraService == null) {
-                Slog.w(TAG, "Virtual camera service is not connected.");
+        synchronized (mCameras) {
+            IBinder binder = cameraConfig.getCallback().asBinder();
+            if (!mCameras.containsKey(binder)) {
+                Slog.w(TAG, "Virtual camera was not registered.");
             } else {
-                mVirtualCameraService.unregisterCamera(cameraConfig.getCallback().asBinder());
+                connectVirtualCameraServiceIfNeeded();
+
+                try {
+                    synchronized (mServiceLock) {
+                        mVirtualCameraService.unregisterCamera(binder);
+                    }
+                    mCameras.remove(binder);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
             }
-            synchronized (mCameras) {
-                mCameras.remove(cameraConfig);
+        }
+    }
+
+    /** Return the id of the virtual camera with the given config. */
+    public int getCameraId(@NonNull VirtualCameraConfig cameraConfig) {
+        connectVirtualCameraServiceIfNeeded();
+
+        try {
+            synchronized (mServiceLock) {
+                return mVirtualCameraService.getCameraId(cameraConfig.getCallback().asBinder());
             }
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            throw e.rethrowFromSystemServer();
         }
     }
 
     @Override
     public void binderDied() {
         Slog.d(TAG, "Virtual camera service died.");
-        mVirtualCameraService = null;
+        synchronized (mServiceLock) {
+            mVirtualCameraService = null;
+        }
         synchronized (mCameras) {
             mCameras.clear();
         }
@@ -122,32 +142,48 @@
     /** Release resources associated with this controller. */
     public void close() {
         synchronized (mCameras) {
-            if (mVirtualCameraService == null) {
-                Slog.w(TAG, "Virtual camera service is not connected.");
-            } else {
-                for (VirtualCameraConfig config : mCameras) {
-                    try {
-                        mVirtualCameraService.unregisterCamera(config.getCallback().asBinder());
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "close(): Camera failed to be removed on camera "
-                                + "service.", e);
+            if (!mCameras.isEmpty()) {
+                connectVirtualCameraServiceIfNeeded();
+
+                synchronized (mServiceLock) {
+                    for (IBinder binder : mCameras.keySet()) {
+                        try {
+                            mVirtualCameraService.unregisterCamera(binder);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "close(): Camera failed to be removed on camera "
+                                    + "service.", e);
+                        }
                     }
                 }
+                mCameras.clear();
             }
-            mCameras.clear();
         }
-        mVirtualCameraService = null;
+        synchronized (mServiceLock) {
+            mVirtualCameraService = null;
+        }
     }
 
     /** Dumps information about this {@link VirtualCameraController} for debugging purposes. */
     public void dump(PrintWriter fout, String indent) {
         fout.println(indent + "VirtualCameraController:");
         indent += indent;
-        fout.printf("%sService:%s\n", indent, mVirtualCameraService);
         synchronized (mCameras) {
             fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size());
-            for (VirtualCameraConfig config : mCameras) {
-                fout.printf("%s token: %s\n", indent, config);
+            for (CameraDescriptor descriptor : mCameras.values()) {
+                fout.printf("%s token: %s\n", indent, descriptor.mConfig);
+            }
+        }
+    }
+
+    private void connectVirtualCameraServiceIfNeeded() {
+        synchronized (mServiceLock) {
+            // Try to connect to service if not connected already.
+            if (mVirtualCameraService == null) {
+                connectVirtualCameraService();
+            }
+            // Throw exception if we are unable to connect to service.
+            if (mVirtualCameraService == null) {
+                throw new IllegalStateException("Virtual camera service is not connected.");
             }
         }
     }
@@ -173,7 +209,24 @@
 
     private boolean registerCameraWithService(VirtualCameraConfig config) throws RemoteException {
         VirtualCameraConfiguration serviceConfiguration = getServiceCameraConfiguration(config);
-        return mVirtualCameraService.registerCamera(config.getCallback().asBinder(),
-                serviceConfiguration);
+        synchronized (mServiceLock) {
+            return mVirtualCameraService.registerCamera(config.getCallback().asBinder(),
+                    serviceConfiguration);
+        }
+    }
+
+    private final class CameraDescriptor implements IBinder.DeathRecipient {
+
+        private final VirtualCameraConfig mConfig;
+
+        CameraDescriptor(VirtualCameraConfig config) {
+            mConfig = config;
+        }
+
+        @Override
+        public void binderDied() {
+            Slog.d(TAG, "Virtual camera binder died");
+            unregisterCamera(mConfig);
+        }
     }
 }
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index fc56511..23c008e 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -27,6 +27,7 @@
 import android.content.LocusId;
 import android.content.res.Configuration;
 import android.os.IBinder;
+import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -134,6 +135,18 @@
             @Nullable LocusId locusId, @NonNull IBinder appToken);
 
     /**
+     * Report a user interaction event to UsageStatsManager
+     *
+     * @param pkgName The package for which this user interaction event occurred.
+     * @param userId The user id to which component belongs to.
+     * @param extras The extra details about this user interaction event.
+     * {@link UsageEvents.Event#USER_INTERACTION}
+     * {@link UsageStatsManager#reportUserInteraction(String, int, PersistableBundle)}
+     */
+    public abstract void reportUserInteractionEvent(@NonNull String pkgName, @UserIdInt int userId,
+            @NonNull PersistableBundle extras);
+
+    /**
      * Prepares the UsageStatsService for shutdown.
      */
     public abstract void prepareShutdown();
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 77b6d583..eb3ec24 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1466,8 +1466,11 @@
         if (android.security.Flags.binaryTransparencySepolicyHash()) {
             byte[] sepolicyHash = PackageUtils.computeSha256DigestForLargeFileAsBytes(
                     "/sys/fs/selinux/policy", PackageUtils.createLargeFileBuffer());
-            String sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false);
-            Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded);
+            String sepolicyHashEncoded = null;
+            if (sepolicyHash != null) {
+                sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false);
+                Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded);
+            }
             FrameworkStatsLog.write(FrameworkStatsLog.BOOT_INTEGRITY_INFO_REPORTED,
                     sepolicyHashEncoded, mVbmetaDigest);
         }
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 7fe0682..c258370 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,5 +1,5 @@
-# BootReceiver
-per-file BootReceiver.java = gaillard@google.com
+# BootReceiver / Watchdog
+per-file BootReceiver.java,Watchdog.java = gaillard@google.com
 
 # Connectivity / Networking
 per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java,VpnManagerService.java = file:/services/core/java/com/android/server/net/OWNERS
diff --git a/services/core/java/com/android/server/SecurityStateManagerService.java b/services/core/java/com/android/server/SecurityStateManagerService.java
new file mode 100644
index 0000000..98039be
--- /dev/null
+++ b/services/core/java/com/android/server/SecurityStateManagerService.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import static android.os.SecurityStateManager.KEY_KERNEL_VERSION;
+import static android.os.SecurityStateManager.KEY_SYSTEM_SPL;
+import static android.os.SecurityStateManager.KEY_VENDOR_SPL;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.ISecurityStateManager;
+import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewUpdateService;
+
+import com.android.internal.R;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SecurityStateManagerService extends ISecurityStateManager.Stub {
+
+    private static final String TAG = "SecurityStateManagerService";
+
+    static final String VENDOR_SECURITY_PATCH_PROPERTY_KEY = "ro.vendor.build"
+            + ".security_patch";
+    static final Pattern KERNEL_RELEASE_PATTERN = Pattern.compile("(\\d+\\.\\d+\\.\\d+)("
+            + ".*)");
+
+    private final Context mContext;
+    private final PackageManager mPackageManager;
+
+    public SecurityStateManagerService(Context context) {
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+    }
+
+    @Override
+    public Bundle getGlobalSecurityState() {
+        Bundle globalSecurityState = new Bundle();
+        globalSecurityState.putString(KEY_SYSTEM_SPL, Build.VERSION.SECURITY_PATCH);
+        globalSecurityState.putString(KEY_VENDOR_SPL,
+                SystemProperties.get(VENDOR_SECURITY_PATCH_PROPERTY_KEY, ""));
+        String moduleMetadataProviderPackageName =
+                mContext.getString(R.string.config_defaultModuleMetadataProvider);
+        if (!moduleMetadataProviderPackageName.isEmpty()) {
+            globalSecurityState.putString(moduleMetadataProviderPackageName,
+                    getSpl(moduleMetadataProviderPackageName));
+        }
+        globalSecurityState.putString(KEY_KERNEL_VERSION, getKernelVersion());
+        addWebViewPackages(globalSecurityState);
+        addSecurityStatePackages(globalSecurityState);
+        return globalSecurityState;
+    }
+
+    private String getSpl(String packageName) {
+        if (!TextUtils.isEmpty(packageName)) {
+            try {
+                return mPackageManager.getPackageInfo(packageName, 0 /* flags */).versionName;
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.e(TAG, TextUtils.formatSimple("Failed to get SPL for package %s.",
+                        packageName), e);
+            }
+        }
+        return "";
+    }
+
+    private String getKernelVersion() {
+        Matcher matcher = KERNEL_RELEASE_PATTERN.matcher(VintfRuntimeInfo.getKernelVersion());
+        if (matcher.matches()) {
+            return matcher.group(1);
+        }
+        return "";
+    }
+
+    private void addWebViewPackages(Bundle bundle) {
+        for (WebViewProviderInfo info : WebViewUpdateService.getAllWebViewPackages()) {
+            String packageName = info.packageName;
+            bundle.putString(packageName, getSpl(packageName));
+        }
+    }
+
+    private void addSecurityStatePackages(Bundle bundle) {
+        String[] packageNames;
+        packageNames = mContext.getResources().getStringArray(R.array.config_securityStatePackages);
+        for (String packageName : packageNames) {
+            bundle.putString(packageName, getSpl(packageName));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index f6835fe..39b8643 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -215,7 +215,7 @@
     public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10;
 
     /** Extended timeout for the system server watchdog. */
-    private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 60 * 1000;
+    private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 20 * 1000;
 
     /** Extended timeout for the system server watchdog for vold#partition operation. */
     private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000;
@@ -1235,11 +1235,16 @@
         }
     }
 
+    private void extendWatchdogTimeout(String reason) {
+        Watchdog w = Watchdog.getInstance();
+        w.pauseWatchingMonitorsFor(SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, reason);
+        w.pauseWatchingCurrentThreadFor(SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, reason);
+    }
+
     private void onUserStopped(int userId) {
         Slog.d(TAG, "onUserStopped " + userId);
 
-        Watchdog.getInstance().pauseWatchingMonitorsFor(
-                SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
+        extendWatchdogTimeout("#onUserStopped might be slow");
         try {
             mVold.onUserStopped(userId);
             mStoraged.onUserStopped(userId);
@@ -1322,8 +1327,7 @@
                 unlockedUsers.add(userId);
             }
         }
-        Watchdog.getInstance().pauseWatchingMonitorsFor(
-                SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
+        extendWatchdogTimeout("#onUserStopped might be slow");
         for (Integer userId : unlockedUsers) {
             try {
                 mVold.onUserStopped(userId);
@@ -2343,8 +2347,7 @@
         try {
             // TODO(b/135341433): Remove cautious logging when FUSE is stable
             Slog.i(TAG, "Mounting volume " + vol);
-            Watchdog.getInstance().pauseWatchingMonitorsFor(
-                    SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow");
+            extendWatchdogTimeout("#mount might be slow");
             mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
                 @Override
                 public boolean onVolumeChecking(FileDescriptor fd, String path,
@@ -2474,8 +2477,7 @@
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
 
-        Watchdog.getInstance().pauseWatchingMonitorsFor(
-                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
+        extendWatchdogTimeout("#partition might be slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
             waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2493,8 +2495,7 @@
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
 
-        Watchdog.getInstance().pauseWatchingMonitorsFor(
-                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
+        extendWatchdogTimeout("#partition might be slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
             waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2512,8 +2513,7 @@
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
 
-        Watchdog.getInstance().pauseWatchingMonitorsFor(
-                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
+        extendWatchdogTimeout("#partition might be slow");
         try {
             mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
             waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -3622,8 +3622,7 @@
 
         @Override
         public ParcelFileDescriptor open() throws AppFuseMountException {
-            Watchdog.getInstance().pauseWatchingMonitorsFor(
-                SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#open might be slow");
+            extendWatchdogTimeout("#open might be slow");
             try {
                 final FileDescriptor fd = mVold.mountAppFuse(uid, mountId);
                 mMounted = true;
@@ -3636,8 +3635,7 @@
         @Override
         public ParcelFileDescriptor openFile(int mountId, int fileId, int flags)
                 throws AppFuseMountException {
-            Watchdog.getInstance().pauseWatchingMonitorsFor(
-                SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#openFile might be slow");
+            extendWatchdogTimeout("#openFile might be slow");
             try {
                 return new ParcelFileDescriptor(
                         mVold.openAppFuseFile(uid, mountId, fileId, flags));
@@ -3648,8 +3646,7 @@
 
         @Override
         public void close() throws Exception {
-            Watchdog.getInstance().pauseWatchingMonitorsFor(
-                SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#close might be slow");
+            extendWatchdogTimeout("#close might be slow");
             if (mMounted) {
                 BackgroundThread.getHandler().post(() -> {
                     try {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index c718d39..9eb35fd 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2096,14 +2096,48 @@
     /**
      * Send a notification to registrants about the data activity state.
      *
+     * @param subId the subscriptionId for the data connection
+     * @param state indicates the latest data activity type
+     * e.g.,{@link TelephonyManager#DATA_ACTIVITY_IN}
+     *
+     */
+
+    public void notifyDataActivityForSubscriber(int subId, int state) {
+        if (!checkNotifyPermission("notifyDataActivity()")) {
+            return;
+        }
+        int phoneId = getPhoneIdFromSubId(subId);
+        synchronized (mRecords) {
+            if (validatePhoneId(phoneId)) {
+                mDataActivity[phoneId] = state;
+                for (Record r : mRecords) {
+                    // Notify by correct subId.
+                    if (r.matchTelephonyCallbackEvent(
+                            TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED)
+                            && idMatch(r, subId, phoneId)) {
+                        try {
+                            r.callback.onDataActivity(state);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    /**
+     * Send a notification to registrants about the data activity state.
+     *
      * @param phoneId the phoneId carrying the data connection
      * @param subId the subscriptionId for the data connection
      * @param state indicates the latest data activity type
      * e.g.,{@link TelephonyManager#DATA_ACTIVITY_IN}
      *
      */
-    public void notifyDataActivityForSubscriber(int phoneId, int subId, int state) {
-        if (!checkNotifyPermission("notifyDataActivity()" )) {
+    public void notifyDataActivityForSubscriberWithSlot(int phoneId, int subId, int state) {
+        if (!checkNotifyPermission("notifyDataActivityWithSlot()")) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 003046a..60f087f 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -69,6 +69,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.time.Clock;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -98,11 +99,17 @@
     //         applications may not work with a debug build. CTS will fail.
     private static final long DEFAULT_TIMEOUT = DB ? 10 * 1000 : 60 * 1000;
 
+    // This ratio is used to compute the pre-watchdog timeout (2 means that the pre-watchdog timeout
+    // will be half the full timeout).
+    //
+    // The pre-watchdog event is similar to a full watchdog except it does not crash system server.
+    private static final int PRE_WATCHDOG_TIMEOUT_RATIO = 2;
+
     // These are temporally ordered: larger values as lateness increases
-    private static final int COMPLETED = 0;
-    private static final int WAITING = 1;
-    private static final int WAITED_HALF = 2;
-    private static final int OVERDUE = 3;
+    static final int COMPLETED = 0;
+    static final int WAITING = 1;
+    static final int WAITED_UNTIL_PRE_WATCHDOG = 2;
+    static final int OVERDUE = 3;
 
     // Track watchdog timeout history and break the crash loop if there is.
     private static final String TIMEOUT_HISTORY_FILE = "/data/system/watchdog-timeout-history.txt";
@@ -237,10 +244,8 @@
         }
     }
 
-    /**
-     * Used for checking status of handle threads and scheduling monitor callbacks.
-     */
-    public final class HandlerChecker implements Runnable {
+    /** Used for checking status of handle threads and scheduling monitor callbacks. */
+    public static class HandlerChecker implements Runnable {
         private final Handler mHandler;
         private final String mName;
         private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
@@ -251,11 +256,19 @@
         private long mStartTimeMillis;
         private int mPauseCount;
         private long mPauseEndTimeMillis;
+        private Clock mClock;
+        private Object mLock;
 
-        HandlerChecker(Handler handler, String name) {
+        HandlerChecker(Handler handler, String name, Object lock, Clock clock) {
             mHandler = handler;
             mName = name;
+            mLock = lock;
             mCompleted = true;
+            mClock = clock;
+        }
+
+        HandlerChecker(Handler handler, String name, Object lock) {
+            this(handler, name, lock, SystemClock.uptimeClock());
         }
 
         void addMonitorLocked(Monitor monitor) {
@@ -278,11 +291,9 @@
                 mMonitorQueue.clear();
             }
 
-            long nowMillis = SystemClock.uptimeMillis();
-            boolean isPaused = mPauseCount > 0
-                    || (mPauseEndTimeMillis > 0 && mPauseEndTimeMillis < nowMillis);
-            if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
-                    || isPaused) {
+            long nowMillis = mClock.millis();
+            boolean isPaused = mPauseCount > 0 || mPauseEndTimeMillis > nowMillis;
+            if ((mMonitors.size() == 0 && isHandlerPolling()) || isPaused) {
                 // Don't schedule until after resume OR
                 // If the target looper has recently been polling, then
                 // there is no reason to enqueue our checker on it since that
@@ -305,15 +316,19 @@
             mHandler.postAtFrontOfQueue(this);
         }
 
+        boolean isHandlerPolling() {
+            return mHandler.getLooper().getQueue().isPolling();
+        }
+
         public int getCompletionStateLocked() {
             if (mCompleted) {
                 return COMPLETED;
             } else {
-                long latency = SystemClock.uptimeMillis() - mStartTimeMillis;
-                if (latency < mWaitMaxMillis / 2) {
+                long latency = mClock.millis() - mStartTimeMillis;
+                if (latency < mWaitMaxMillis / PRE_WATCHDOG_TIMEOUT_RATIO) {
                     return WAITING;
                 } else if (latency < mWaitMaxMillis) {
-                    return WAITED_HALF;
+                    return WAITED_UNTIL_PRE_WATCHDOG;
                 }
             }
             return OVERDUE;
@@ -334,7 +349,7 @@
             } else {
                 prefix =  "Blocked in monitor " + mCurrentMonitor.getClass().getName();
             }
-            long latencySeconds = (SystemClock.uptimeMillis() - mStartTimeMillis) / 1000;
+            long latencySeconds = (mClock.millis() - mStartTimeMillis) / 1000;
             return prefix + " on " + mName + " (" + getThread().getName() + ")"
                 + " for " + latencySeconds + "s";
         }
@@ -366,10 +381,11 @@
          * the given time.
          */
         public void pauseForLocked(int pauseMillis, String reason) {
-            mPauseEndTimeMillis = SystemClock.uptimeMillis() + pauseMillis;
+            mPauseEndTimeMillis = mClock.millis() + pauseMillis;
             // Mark as completed, because there's a chance we called this after the watchog
-            // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
-            // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
+            // thread loop called Object#wait after 'WAITED_UNTIL_PRE_WATCHDOG'. In that case we
+            // want to ensure the next call to #getCompletionStateLocked for this checker returns
+            // 'COMPLETED'
             mCompleted = true;
             Slog.i(TAG, "Pausing of HandlerChecker: " + mName + " for reason: "
                     + reason + ". Pause end time: " + mPauseEndTimeMillis);
@@ -379,8 +395,9 @@
         public void pauseLocked(String reason) {
             mPauseCount++;
             // Mark as completed, because there's a chance we called this after the watchog
-            // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
-            // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
+            // thread loop called Object#wait after 'WAITED_UNTIL_PRE_WATCHDOG'. In that case we
+            // want to ensure the next call to #getCompletionStateLocked for this checker returns
+            // 'COMPLETED'
             mCompleted = true;
             Slog.i(TAG, "Pausing HandlerChecker: " + mName + " for reason: "
                     + reason + ". Pause count: " + mPauseCount);
@@ -396,6 +413,11 @@
                 Slog.wtf(TAG, "Already resumed HandlerChecker: " + mName);
             }
         }
+
+        @Override
+        public String toString() {
+            return "CheckerHandler for " + mName;
+        }
     }
 
     final class RebootRequestReceiver extends BroadcastReceiver {
@@ -445,31 +467,40 @@
         ServiceThread t = new ServiceThread("watchdog.monitor",
                 android.os.Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/);
         t.start();
-        mMonitorChecker = new HandlerChecker(new Handler(t.getLooper()), "monitor thread");
+        mMonitorChecker = new HandlerChecker(new Handler(t.getLooper()), "monitor thread", mLock);
         mHandlerCheckers.add(withDefaultTimeout(mMonitorChecker));
 
-        mHandlerCheckers.add(withDefaultTimeout(
-                new HandlerChecker(FgThread.getHandler(), "foreground thread")));
+        mHandlerCheckers.add(
+                withDefaultTimeout(
+                        new HandlerChecker(FgThread.getHandler(), "foreground thread", mLock)));
         // Add checker for main thread.  We only do a quick check since there
         // can be UI running on the thread.
-        mHandlerCheckers.add(withDefaultTimeout(
-                new HandlerChecker(new Handler(Looper.getMainLooper()), "main thread")));
+        mHandlerCheckers.add(
+                withDefaultTimeout(
+                        new HandlerChecker(
+                                new Handler(Looper.getMainLooper()), "main thread", mLock)));
         // Add checker for shared UI thread.
-        mHandlerCheckers.add(withDefaultTimeout(
-                new HandlerChecker(UiThread.getHandler(), "ui thread")));
+        mHandlerCheckers.add(
+                withDefaultTimeout(new HandlerChecker(UiThread.getHandler(), "ui thread", mLock)));
         // And also check IO thread.
-        mHandlerCheckers.add(withDefaultTimeout(
-                new HandlerChecker(IoThread.getHandler(), "i/o thread")));
+        mHandlerCheckers.add(
+                withDefaultTimeout(new HandlerChecker(IoThread.getHandler(), "i/o thread", mLock)));
         // And the display thread.
-        mHandlerCheckers.add(withDefaultTimeout(
-                new HandlerChecker(DisplayThread.getHandler(), "display thread")));
+        mHandlerCheckers.add(
+                withDefaultTimeout(
+                        new HandlerChecker(DisplayThread.getHandler(), "display thread", mLock)));
         // And the animation thread.
-        mHandlerCheckers.add(withDefaultTimeout(
-                 new HandlerChecker(AnimationThread.getHandler(), "animation thread")));
+        mHandlerCheckers.add(
+                withDefaultTimeout(
+                        new HandlerChecker(
+                                AnimationThread.getHandler(), "animation thread", mLock)));
         // And the surface animation thread.
-        mHandlerCheckers.add(withDefaultTimeout(
-                new HandlerChecker(SurfaceAnimationThread.getHandler(),
-                    "surface animation thread")));
+        mHandlerCheckers.add(
+                withDefaultTimeout(
+                        new HandlerChecker(
+                                SurfaceAnimationThread.getHandler(),
+                                "surface animation thread",
+                                mLock)));
         // Initialize monitor for Binder threads.
         addMonitor(new BinderThreadMonitor());
 
@@ -609,7 +640,7 @@
     public void addThread(Handler thread) {
         synchronized (mLock) {
             final String name = thread.getLooper().getThread().getName();
-            mHandlerCheckers.add(withDefaultTimeout(new HandlerChecker(thread, name)));
+            mHandlerCheckers.add(withDefaultTimeout(new HandlerChecker(thread, name, mLock)));
         }
     }
 
@@ -617,7 +648,7 @@
         synchronized (mLock) {
             final String name = thread.getLooper().getThread().getName();
             mHandlerCheckers.add(
-                    withCustomTimeout(new HandlerChecker(thread, name), timeoutMillis));
+                    withCustomTimeout(new HandlerChecker(thread, name, mLock), timeoutMillis));
         }
     }
 
@@ -797,11 +828,11 @@
             String subject = "";
             boolean allowRestart = true;
             int debuggerWasConnected = 0;
-            boolean doWaitedHalfDump = false;
+            boolean doWaitedPreDump = false;
             // The value of mWatchdogTimeoutMillis might change while we are executing the loop.
             // We store the current value to use a consistent value for all handlers.
             final long watchdogTimeoutMillis = mWatchdogTimeoutMillis;
-            final long checkIntervalMillis = watchdogTimeoutMillis / 2;
+            final long checkIntervalMillis = watchdogTimeoutMillis / PRE_WATCHDOG_TIMEOUT_RATIO;
             final ArrayList<Integer> pids;
             synchronized (mLock) {
                 long timeout = checkIntervalMillis;
@@ -848,15 +879,16 @@
                 } else if (waitState == WAITING) {
                     // still waiting but within their configured intervals; back off and recheck
                     continue;
-                } else if (waitState == WAITED_HALF) {
+                } else if (waitState == WAITED_UNTIL_PRE_WATCHDOG) {
                     if (!waitedHalf) {
-                        Slog.i(TAG, "WAITED_HALF");
+                        Slog.i(TAG, "WAITED_UNTIL_PRE_WATCHDOG");
                         waitedHalf = true;
-                        // We've waited half, but we'd need to do the stack trace dump w/o the lock.
-                        blockedCheckers = getCheckersWithStateLocked(WAITED_HALF);
+                        // We've waited until the pre-watchdog, but we'd need to do the stack trace
+                        // dump w/o the lock.
+                        blockedCheckers = getCheckersWithStateLocked(WAITED_UNTIL_PRE_WATCHDOG);
                         subject = describeCheckersLocked(blockedCheckers);
                         pids = new ArrayList<>(mInterestingJavaPids);
-                        doWaitedHalfDump = true;
+                        doWaitedPreDump = true;
                     } else {
                         continue;
                     }
@@ -874,12 +906,12 @@
             // First collect stack traces from all threads of the system process.
             //
             // Then, if we reached the full timeout, kill this process so that the system will
-            // restart. If we reached half of the timeout, just log some information and continue.
-            logWatchog(doWaitedHalfDump, subject, pids);
+            // restart. If we reached pre-watchdog timeout, just log some information and continue.
+            logWatchog(doWaitedPreDump, subject, pids);
 
-            if (doWaitedHalfDump) {
-                // We have waited for only half of the timeout, we continue to wait for the duration
-                // of the full timeout before killing the process.
+            if (doWaitedPreDump) {
+                // We have waited for only pre-watchdog timeout, we continue to wait for the
+                // duration of the full timeout before killing the process.
                 continue;
             }
 
@@ -928,8 +960,8 @@
         }
     }
 
-    private void logWatchog(boolean halfWatchdog, String subject, ArrayList<Integer> pids) {
-        // Get critical event log before logging the half watchdog so that it doesn't
+    private void logWatchog(boolean preWatchdog, String subject, ArrayList<Integer> pids) {
+        // Get critical event log before logging the pre-watchdog so that it doesn't
         // occur in the log.
         String criticalEvents =
                 CriticalEventLog.getInstance().logLinesForSystemServerTraceFile();
@@ -941,7 +973,7 @@
         }
 
         final String dropboxTag;
-        if (halfWatchdog) {
+        if (preWatchdog) {
             dropboxTag = "pre_watchdog";
             CriticalEventLog.getInstance().logHalfWatchdog(subject);
             FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_PRE_WATCHDOG_OCCURRED);
@@ -971,7 +1003,7 @@
         report.append(processCpuTracker.printCurrentState(anrTime, 10));
         report.append(tracesFileException.getBuffer());
 
-        if (!halfWatchdog) {
+        if (!preWatchdog) {
             // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the
             // kernel log
             doSysRq('w');
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 54c8ed3..b87d02d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -79,7 +79,6 @@
 import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
 import static android.os.PowerExemptionManager.REASON_BOOT_COMPLETED;
 import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
-import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
 import static android.os.PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED;
 import static android.os.PowerExemptionManager.REASON_PROC_STATE_BTOP;
@@ -5051,7 +5050,7 @@
                     REASON_LOCKED_BOOT_COMPLETED);
         }
         // Send BOOT_COMPLETED if the user is unlocked
-        if (StorageManager.isUserKeyUnlocked(app.userId)) {
+        if (StorageManager.isCeStorageUnlocked(app.userId)) {
             sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_BOOT_COMPLETED),
                     REASON_BOOT_COMPLETED);
         }
@@ -14150,7 +14149,8 @@
                                 || action.startsWith("android.intent.action.PACKAGE_")
                                 || action.startsWith("android.intent.action.UID_")
                                 || action.startsWith("android.intent.action.EXTERNAL_")
-                                || action.startsWith("android.bluetooth.")) {
+                                || action.startsWith("android.bluetooth.")
+                                || action.equals(Intent.ACTION_SHUTDOWN)) {
                             if (DEBUG_BROADCAST) {
                                 Slog.wtf(TAG,
                                         "System internals registering for " + filter.toLongString()
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index e656030..1928780 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -3013,7 +3013,7 @@
                                         mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                                         null, mStats.mHandler, null, null,
                                         mUserManagerUserInfoProvider, mPowerProfile,
-                                        mCpuScalingPolicies, null);
+                                        mCpuScalingPolicies, new PowerStatsUidResolver());
                                 checkinStats.readSummaryFromParcel(in);
                                 in.recycle();
                                 checkinStats.dumpProtoLocked(mContext, fd, apps, flags,
@@ -3055,7 +3055,7 @@
                                         mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
                                         null, mStats.mHandler, null, null,
                                         mUserManagerUserInfoProvider, mPowerProfile,
-                                        mCpuScalingPolicies, null);
+                                        mCpuScalingPolicies, new PowerStatsUidResolver());
                                 checkinStats.readSummaryFromParcel(in);
                                 in.recycle();
                                 checkinStats.dumpCheckin(mContext, pw, apps, flags,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 192fd6f..69e3aaf 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -152,11 +152,13 @@
         "mainline_sdk",
         "media_audio",
         "media_drm",
+        "media_reliability",
         "media_tv",
         "media_solutions",
         "nfc",
         "pdf_viewer",
         "pixel_audio_android",
+        "pixel_bluetooth",
         "pixel_system_sw_touch",
         "pixel_watch",
         "platform_security",
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d4b72c1..5d47e35 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4600,7 +4600,7 @@
     }
 
     private void setStreamVolume(int streamType, int index, int flags,
-            @NonNull AudioDeviceAttributes ada,
+            @Nullable AudioDeviceAttributes ada,
             String callingPackage, String caller, String attributionTag, int uid,
             boolean hasModifyAudioSettings,
             boolean canChangeMuteAndUpdateController) {
@@ -4618,7 +4618,9 @@
         int streamTypeAlias = mStreamVolumeAlias[streamType];
         VolumeStreamState streamState = mStreamStates[streamTypeAlias];
 
-        final int device = ada.getInternalType();
+        final int device = (ada == null)
+                ? getDeviceForStream(streamType)
+                : ada.getInternalType();
         int oldIndex;
 
         // skip a2dp absolute volume control request when the device
@@ -10665,8 +10667,8 @@
 
     /** @see LoudnessCodecConfigurator#addMediaCodec(MediaCodec) */
     @Override
-    public void addLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) {
-        mLoudnessCodecHelper.addLoudnessCodecInfo(piid, codecInfo);
+    public void addLoudnessCodecInfo(int piid, int mediaCodecHash, LoudnessCodecInfo codecInfo) {
+        mLoudnessCodecHelper.addLoudnessCodecInfo(piid, mediaCodecHash, codecInfo);
     }
 
     /** @see LoudnessCodecConfigurator#removeMediaCodec(MediaCodec) */
@@ -11370,6 +11372,8 @@
     static final int LOG_NB_EVENTS_SPATIAL = 30;
     static final int LOG_NB_EVENTS_SOUND_DOSE = 30;
 
+    static final int LOG_NB_EVENTS_LOUDNESS_CODEC = 30;
+
     static final EventLogger
             sLifecycleLogger = new EventLogger(LOG_NB_EVENTS_LIFECYCLE,
             "audio services lifecycle");
@@ -11569,6 +11573,10 @@
         mSpatializerHelper.dump(pw);
         sSpatialLogger.dump(pw);
 
+        pw.println("\n");
+        pw.println("\nLoudness alignment:");
+        mLoudnessCodecHelper.dump(pw);
+
         mAudioSystem.dump(pw);
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index f69b9f6..de89011 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -622,6 +622,55 @@
         }
     }
 
+    static final class LoudnessEvent extends EventLogger.Event {
+        static final int START_PIID = 0;
+
+        static final int STOP_PIID = 1;
+
+        static final int CLIENT_DIED = 2;
+
+        final int mEventType;
+        final int mIntValue1;
+        final int mIntValue2;
+
+        private LoudnessEvent(int event, int i1, int i2) {
+            mEventType = event;
+            mIntValue1 = i1;
+            mIntValue2 = i2;
+        }
+
+        static LoudnessEvent getStartPiid(int piid, int pid) {
+            return new LoudnessEvent(START_PIID, piid, pid);
+        }
+
+        static LoudnessEvent getStopPiid(int piid, int pid) {
+            return new LoudnessEvent(STOP_PIID, piid, pid);
+        }
+
+        static LoudnessEvent getClientDied(int pid) {
+            return new LoudnessEvent(CLIENT_DIED, 0 /* ignored */, pid);
+        }
+
+
+        @Override
+        public String eventToString() {
+            switch (mEventType) {
+                case START_PIID:
+                    return String.format(
+                            "Start loudness updates for piid %d for client pid %d",
+                            mIntValue1, mIntValue2);
+                case STOP_PIID:
+                    return String.format(
+                            "Stop loudness updates for piid %d for client pid %d",
+                            mIntValue1, mIntValue2);
+                case CLIENT_DIED:
+                    return String.format("Loudness client with pid %d died", mIntValue2);
+
+            }
+            return new StringBuilder("FIXME invalid event type:").append(mEventType).toString();
+        }
+    }
+
     /**
      * Class to log stream type mute/unmute events
      */
diff --git a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
index 3c67e9d..bbe819f 100644
--- a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
+++ b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
@@ -21,6 +21,11 @@
 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID;
 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH;
 import static android.media.AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID;
+import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4;
+import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D;
+import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE;
+import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION;
+import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -41,7 +46,11 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.audio.AudioServiceEvents.LoudnessEvent;
+import com.android.server.utils.EventLogger;
 
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -50,6 +59,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Class to handle the updates in loudness parameters and responsible to generate parameters that
@@ -70,10 +80,14 @@
     private static final String SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE =
             "audio.loudness.builtin-speaker-spl-range-size";
 
-    private static final int SPL_RANGE_UNKNOWN = 0;
-    private static final int SPL_RANGE_SMALL = 1;
-    private static final int SPL_RANGE_MEDIUM = 2;
-    private static final int SPL_RANGE_LARGE = 3;
+    @VisibleForTesting
+    static final int SPL_RANGE_UNKNOWN = 0;
+    @VisibleForTesting
+    static final int SPL_RANGE_SMALL = 1;
+    @VisibleForTesting
+    static final int SPL_RANGE_MEDIUM = 2;
+    @VisibleForTesting
+    static final int SPL_RANGE_LARGE = 3;
 
     /** The possible transducer SPL ranges as defined in CTA2075 */
     @IntDef({
@@ -99,12 +113,19 @@
                 pid = (Integer) cookie;
             }
             if (pid != null) {
-                mLoudnessCodecHelper.removePid(pid);
+                if (DEBUG) {
+                    Log.d(TAG, "Client with pid " + pid + " died, removing from receiving updates");
+                }
+                sLogger.enqueue(LoudnessEvent.getClientDied(pid));
+                mLoudnessCodecHelper.onClientPidDied(pid);
             }
             super.onCallbackDied(callback, cookie);
         }
     }
 
+    private static final EventLogger sLogger = new EventLogger(
+            AudioService.LOG_NB_EVENTS_LOUDNESS_CODEC, "Loudness updates");
+
     private final LoudnessRemoteCallbackList mLoudnessUpdateDispatchers =
             new LoudnessRemoteCallbackList(this);
 
@@ -125,7 +146,8 @@
     private final AudioService mAudioService;
 
     /** Contains the properties necessary to compute the codec loudness related parameters. */
-    private static final class LoudnessCodecInputProperties {
+    @VisibleForTesting
+    static final class LoudnessCodecInputProperties {
         private final int mMetadataType;
 
         private final boolean mIsDownmixing;
@@ -200,10 +222,53 @@
         }
 
         PersistableBundle createLoudnessParameters() {
-            // TODO: create bundle with new parameters
-            return new PersistableBundle();
-        }
+            PersistableBundle loudnessParams = new PersistableBundle();
 
+            switch (mDeviceSplRange) {
+                case SPL_RANGE_LARGE:
+                    // corresponds to -31dB attenuation
+                    loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 124);
+                    if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) {
+                        loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, 0);
+                    } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) {
+                        // general compression
+                        loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6);
+                    }
+                    break;
+                case SPL_RANGE_MEDIUM:
+                    // corresponds to -24dB attenuation
+                    loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 96);
+                    if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) {
+                        loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, mIsDownmixing ? 1 : 0);
+                    } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) {
+                        // general compression
+                        loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6);
+                    }
+                    break;
+                case SPL_RANGE_SMALL:
+                    // corresponds to -16dB attenuation
+                    loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 64);
+                    if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) {
+                        loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, 1);
+                    } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) {
+                        // limited playback range compression
+                        loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 3);
+                    }
+                    break;
+                default:
+                    // corresponds to -24dB attenuation
+                    loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 96);
+                    if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) {
+                        loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, mIsDownmixing ? 1 : 0);
+                    } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) {
+                        // general compression
+                        loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6);
+                    }
+                    break;
+            }
+
+            return loudnessParams;
+        }
     }
 
     @GuardedBy("mLock")
@@ -227,22 +292,25 @@
         if (DEBUG) {
             Log.d(TAG, "startLoudnessCodecUpdates: piid " + piid + " codecInfos " + codecInfoList);
         }
-        Set<LoudnessCodecInfo> infoSet;
+
         synchronized (mLock) {
             if (mStartedPiids.contains(piid)) {
                 Log.w(TAG, "Already started loudness updates for piid " + piid);
                 return;
             }
-            infoSet = new HashSet<>(codecInfoList);
+            Set<LoudnessCodecInfo> infoSet = new HashSet<>(codecInfoList);
             mStartedPiids.put(piid, infoSet);
 
-            mPiidToPidCache.put(piid, Binder.getCallingPid());
+            int pid = Binder.getCallingPid();
+            mPiidToPidCache.put(piid, pid);
+
+            sLogger.enqueue(LoudnessEvent.getStartPiid(piid, pid));
         }
 
         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
             mAudioService.getActivePlaybackConfigurations().stream().filter(
                     conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent(
-                            apc -> updateCodecParametersForConfiguration(apc, infoSet));
+                    this::updateCodecParametersForConfiguration);
         }
     }
 
@@ -250,20 +318,24 @@
         if (DEBUG) {
             Log.d(TAG, "stopLoudnessCodecUpdates: piid " + piid);
         }
+
         synchronized (mLock) {
             if (!mStartedPiids.contains(piid)) {
                 Log.w(TAG, "Loudness updates are already stopped for piid " + piid);
                 return;
             }
             mStartedPiids.remove(piid);
+
+            sLogger.enqueue(LoudnessEvent.getStopPiid(piid, mPiidToPidCache.get(piid, -1)));
             mPiidToDeviceIdCache.delete(piid);
             mPiidToPidCache.delete(piid);
         }
     }
 
-    void addLoudnessCodecInfo(int piid, LoudnessCodecInfo info) {
+    void addLoudnessCodecInfo(int piid, int mediaCodecHash, LoudnessCodecInfo info) {
         if (DEBUG) {
-            Log.d(TAG, "addLoudnessCodecInfo: piid " + piid + " info " + info);
+            Log.d(TAG, "addLoudnessCodecInfo: piid " + piid + " mcHash " + mediaCodecHash + " info "
+                    + info);
         }
 
         Set<LoudnessCodecInfo> infoSet;
@@ -280,7 +352,20 @@
         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
             mAudioService.getActivePlaybackConfigurations().stream().filter(
                     conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent(
-                            apc -> updateCodecParametersForConfiguration(apc, Set.of(info)));
+                            apc -> {
+                                final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo();
+                                if (deviceInfo != null) {
+                                    PersistableBundle updateBundle = new PersistableBundle();
+                                    synchronized (mLock) {
+                                        updateBundle.putPersistableBundle(
+                                                Integer.toString(mediaCodecHash),
+                                                getCodecBundle_l(deviceInfo, info));
+                                    }
+                                    if (!updateBundle.isDefinitelyEmpty()) {
+                                        dispatchNewLoudnessParameters(piid, updateBundle);
+                                    }
+                                }
+                            });
         }
     }
 
@@ -298,24 +383,6 @@
         }
     }
 
-    void removePid(int pid) {
-        if (DEBUG) {
-            Log.d(TAG, "Removing pid " + pid + " from receiving updates");
-        }
-        synchronized (mLock) {
-            for (int i = 0; i < mPiidToPidCache.size(); ++i) {
-                int piid = mPiidToPidCache.keyAt(i);
-                if (mPiidToPidCache.get(piid) == pid) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Removing piid  " + piid);
-                    }
-                    mStartedPiids.delete(piid);
-                    mPiidToDeviceIdCache.delete(piid);
-                }
-            }
-        }
-    }
-
     PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) {
         if (DEBUG) {
             Log.d(TAG, "getLoudnessParams: piid " + piid + " codecInfo " + codecInfo);
@@ -381,48 +448,77 @@
             }
         }
 
-        updateApcList.forEach(apc -> updateCodecParametersForConfiguration(apc, null));
+        updateApcList.forEach(apc -> updateCodecParametersForConfiguration(apc));
     }
 
-    /** Updates and dispatches the new loudness parameters for the {@code codecInfos} set.
+    /** Updates and dispatches the new loudness parameters for all its registered codecs. */
+    void dump(PrintWriter pw) {
+        // Registered clients
+        pw.println("\nRegistered clients:\n");
+        synchronized (mLock) {
+            for (int i = 0; i < mStartedPiids.size(); ++i) {
+                int piid = mStartedPiids.keyAt(i);
+                int pid = mPiidToPidCache.get(piid, -1);
+                final Set<LoudnessCodecInfo> codecInfos = mStartedPiids.get(piid);
+                pw.println(String.format("Player piid %d pid %d active codec types %s\n", piid,
+                        pid, codecInfos.stream().map(Object::toString).collect(
+                                Collectors.joining(", "))));
+            }
+            pw.println();
+        }
+
+        sLogger.dump(pw);
+        pw.println();
+    }
+
+    private void onClientPidDied(int pid) {
+        synchronized (mLock) {
+            for (int i = 0; i < mPiidToPidCache.size(); ++i) {
+                int piid = mPiidToPidCache.keyAt(i);
+                if (mPiidToPidCache.get(piid) == pid) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Removing piid  " + piid);
+                    }
+                    mStartedPiids.delete(piid);
+                    mPiidToDeviceIdCache.delete(piid);
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates and dispatches the new loudness parameters for the {@code codecInfos} set.
      *
      * @param apc the player configuration for which the loudness parameters are updated.
-     * @param codecInfos the codec info for which the parameters are updated. If {@code null},
-     *                   send updates for all the started codecs assigned to {@code apc}
      */
-    private void updateCodecParametersForConfiguration(AudioPlaybackConfiguration apc,
-            Set<LoudnessCodecInfo> codecInfos) {
+    private void updateCodecParametersForConfiguration(AudioPlaybackConfiguration apc) {
         if (DEBUG) {
-            Log.d(TAG, "updateCodecParametersForConfiguration apc:" + apc + " codecInfos: "
-                    + codecInfos);
+            Log.d(TAG, "updateCodecParametersForConfiguration apc:" + apc);
         }
+
         final PersistableBundle allBundles = new PersistableBundle();
         final int piid = apc.getPlayerInterfaceId();
+
         synchronized (mLock) {
-            if (codecInfos == null) {
-                codecInfos = mStartedPiids.get(piid);
-            }
+            final Set<LoudnessCodecInfo> codecInfos = mStartedPiids.get(piid);
 
             final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo();
             if (codecInfos != null && deviceInfo != null) {
                 for (LoudnessCodecInfo info : codecInfos) {
-                    allBundles.putPersistableBundle(Integer.toString(info.mediaCodecHashCode),
+                    allBundles.putPersistableBundle(Integer.toString(info.hashCode()),
                             getCodecBundle_l(deviceInfo, info));
                 }
             }
         }
 
         if (!allBundles.isDefinitelyEmpty()) {
-            if (DEBUG) {
-                Log.d(TAG, "Dispatching for piid: " + piid + " bundle: " + allBundles);
-            }
             dispatchNewLoudnessParameters(piid, allBundles);
         }
     }
 
     private void dispatchNewLoudnessParameters(int piid, PersistableBundle bundle) {
         if (DEBUG) {
-            Log.d(TAG, "dispatchNewLoudnessParameters: piid " + piid);
+            Log.d(TAG, "dispatchNewLoudnessParameters: piid " + piid + " bundle: " + bundle);
         }
         final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; ++i) {
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index be78ea2..cecde55 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -149,6 +149,14 @@
     public abstract @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId);
 
     /**
+     * Returns the ID of the device which owns the display with the given ID.
+     *
+     * <p>In case the virtual display ID is invalid or doesn't belong to a virtual device, then
+     * {@link android.content.Context#DEVICE_ID_DEFAULT} is returned.</p>
+     */
+    public abstract int getDeviceIdForDisplayId(int displayId);
+
+    /**
      * Gets the persistent ID for the VirtualDevice with the given device ID.
      *
      * @param deviceId which device we're asking about
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index debf828..d848f4b 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -121,6 +121,7 @@
 
         // Display independent, mode dependent values
         float[] brightnessLevelsNits;
+        float[] brightnessLevels = null;
         float[] luxLevels;
         if (isForIdleMode) {
             brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
@@ -130,11 +131,21 @@
         } else {
             brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
             luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
+
+            brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels();
+            if (brightnessLevels == null || brightnessLevels.length == 0) {
+                // Load the old configuration in the range [0, 255]. The values need to be
+                // normalized to the range [0, 1].
+                int[] brightnessLevelsInt = resources.getIntArray(
+                        com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
+                brightnessLevels = new float[brightnessLevelsInt.length];
+                for (int i = 0; i < brightnessLevels.length; i++) {
+                    brightnessLevels[i] = normalizeAbsoluteBrightness(brightnessLevelsInt[i]);
+                }
+            }
         }
 
         // Display independent, mode independent values
-        int[] brightnessLevelsBacklight = resources.getIntArray(
-                com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
         float autoBrightnessAdjustmentMaxGamma = resources.getFraction(
                 com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
                 1, 1);
@@ -155,8 +166,8 @@
             builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
             return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange,
                     autoBrightnessAdjustmentMaxGamma, isForIdleMode, displayWhiteBalanceController);
-        } else if (isValidMapping(luxLevels, brightnessLevelsBacklight) && !isForIdleMode) {
-            return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight,
+        } else if (isValidMapping(luxLevels, brightnessLevels)) {
+            return new SimpleMappingStrategy(luxLevels, brightnessLevels,
                     autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout);
         } else {
             return null;
@@ -620,7 +631,7 @@
         private float mUserBrightness;
         private long mShortTermModelTimeout;
 
-        private SimpleMappingStrategy(float[] lux, int[] brightness, float maxGamma,
+        private SimpleMappingStrategy(float[] lux, float[] brightness, float maxGamma,
                 long timeout) {
             Preconditions.checkArgument(lux.length != 0 && brightness.length != 0,
                     "Lux and brightness arrays must not be empty!");
@@ -635,7 +646,7 @@
             mBrightness = new float[N];
             for (int i = 0; i < N; i++) {
                 mLux[i] = lux[i];
-                mBrightness[i] = normalizeAbsoluteBrightness(brightness[i]);
+                mBrightness[i] = brightness[i];
             }
 
             mMaxGamma = maxGamma;
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 70d4ad2..c26118e 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -20,6 +20,8 @@
 import android.os.Handler;
 import android.view.Display;
 
+import com.android.server.display.feature.DisplayManagerFlags;
+
 import java.io.PrintWriter;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -39,6 +41,7 @@
     private final Handler mHandler;
     private final Listener mListener;
     private final String mName;
+    private final DisplayManagerFlags mFeatureFlags;
 
     public static final int DISPLAY_DEVICE_EVENT_ADDED = 1;
     public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2;
@@ -50,13 +53,14 @@
     private static final AtomicInteger NEXT_DISPLAY_MODE_ID = new AtomicInteger(1);  // 0 = no mode.
 
     // Called with SyncRoot lock held.
-    public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
-            Context context, Handler handler, Listener listener, String name) {
+    DisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler,
+            Listener listener, String name, DisplayManagerFlags featureFlags) {
         mSyncRoot = syncRoot;
         mContext = context;
         mHandler = handler;
         mListener = listener;
         mName = name;
+        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -88,6 +92,10 @@
         return mName;
     }
 
+    public final DisplayManagerFlags getFeatureFlags() {
+        return mFeatureFlags;
+    }
+
     /**
      * Registers the display adapter with the display manager.
      *
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 2fdf90d..3b05b47 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -400,6 +400,7 @@
     }
 
     private DisplayDeviceConfig loadDisplayDeviceConfig() {
-        return DisplayDeviceConfig.create(mContext, false);
+        return DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false,
+                mDisplayAdapter.getFeatureFlags());
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index b99de5c..d97127c 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -57,6 +57,7 @@
 import com.android.server.display.config.HighBrightnessMode;
 import com.android.server.display.config.IntegerArray;
 import com.android.server.display.config.LuxThrottling;
+import com.android.server.display.config.LuxToBrightnessMapping;
 import com.android.server.display.config.NitsMap;
 import com.android.server.display.config.NonNegativeFloatToFloatPoint;
 import com.android.server.display.config.Point;
@@ -77,6 +78,7 @@
 import com.android.server.display.config.ThresholdPoint;
 import com.android.server.display.config.UsiVersion;
 import com.android.server.display.config.XmlParser;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.utils.DebugUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -310,16 +312,18 @@
  *          <darkeningLightDebounceIdleMillis>
  *              1000
  *          </darkeningLightDebounceIdleMillis>
- *          <displayBrightnessMapping>
- *              <displayBrightnessPoint>
- *                  <lux>50</lux>
- *                  <nits>45.32</nits>
- *              </displayBrightnessPoint>
- *              <displayBrightnessPoint>
- *                  <lux>80</lux>
- *                  <nits>75.43</nits>
- *              </displayBrightnessPoint>
- *          </displayBrightnessMapping>
+ *          <luxToBrightnessMapping>
+ *            <map>
+ *              <point>
+ *                <first>0</first>
+ *                <second>0.2</second>
+ *              </point>
+ *              <point>
+ *                <first>80</first>
+ *                <second>0.3</second>
+ *              </point>
+ *            </map>
+ *          </luxToBrightnessMapping>
  *      </autoBrightness>
  *
  *      <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -528,6 +532,7 @@
  *         <majorVersion>2</majorVersion>
  *         <minorVersion>0</minorVersion>
  *     </usiVersion>
+ *     <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode>
  *    </displayConfiguration>
  *  }
  *  </pre>
@@ -629,7 +634,6 @@
     // for the corresponding values above
     private float[] mBrightness;
 
-
     /**
      * Array of desired screen brightness in nits corresponding to the lux values
      * in the mBrightnessLevelsLux array. The display brightness is defined as the
@@ -639,20 +643,25 @@
     private float[] mBrightnessLevelsNits;
 
     /**
-     * Array of light sensor lux values to define our levels for auto backlight
-     * brightness support.
+     * Array of desired screen brightness corresponding to the lux values
+     * in the mBrightnessLevelsLux array. The brightness values must be non-negative and
+     * non-decreasing. They must be between {@link PowerManager.BRIGHTNESS_MIN} and
+     * {@link PowerManager.BRIGHTNESS_MAX}. This must be overridden in platform specific overlays
+     */
+    private float[] mBrightnessLevels;
+
+    /**
+     * Array of light sensor lux values to define our levels for auto-brightness support.
      *
-     * The N + 1 entries of this array define N control points defined in mBrightnessLevelsNits,
-     * with first value always being 0 lux
+     * The first lux value is always 0.
      *
-     * The control points must be strictly increasing.  Each control point
-     * corresponds to an entry in the brightness backlight values arrays.
-     * For example, if lux == level[1] (second element of the levels array)
-     * then the brightness will be determined by value[0] (first element
-     * of the brightness values array).
+     * The control points must be strictly increasing. Each control point corresponds to an entry
+     * in the brightness values arrays. For example, if lux == luxLevels[1] (second element
+     * of the levels array) then the brightness will be determined by brightnessLevels[1] (second
+     * element of the brightness values array).
      *
-     * Spline interpolation is used to determine the auto-brightness
-     * backlight values for lux levels between these control points.
+     * Spline interpolation is used to determine the auto-brightness values for lux levels between
+     * these control points.
      */
     private float[] mBrightnessLevelsLux;
 
@@ -843,9 +852,17 @@
     @Nullable
     private HdrBrightnessData mHdrBrightnessData;
 
+    /**
+     * Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode.
+     */
+    private float mBrightnessCapForWearBedtimeMode;
+
+    private final DisplayManagerFlags mFlags;
+
     @VisibleForTesting
-    DisplayDeviceConfig(Context context) {
+    DisplayDeviceConfig(Context context, DisplayManagerFlags flags) {
         mContext = context;
+        mFlags = flags;
     }
 
     /**
@@ -861,9 +878,9 @@
      * @return A configuration instance for the specified display.
      */
     public static DisplayDeviceConfig create(Context context, long physicalDisplayId,
-            boolean isFirstDisplay) {
+            boolean isFirstDisplay, DisplayManagerFlags flags) {
         final DisplayDeviceConfig config = createWithoutDefaultValues(context, physicalDisplayId,
-                isFirstDisplay);
+                isFirstDisplay, flags);
 
         config.copyUninitializedValuesFromSecondaryConfig(loadDefaultConfigurationXml(context));
         return config;
@@ -878,28 +895,29 @@
      *                     or the default values.
      * @return A configuration instance.
      */
-    public static DisplayDeviceConfig create(Context context, boolean useConfigXml) {
+    public static DisplayDeviceConfig create(Context context, boolean useConfigXml,
+            DisplayManagerFlags flags) {
         final DisplayDeviceConfig config;
         if (useConfigXml) {
-            config = getConfigFromGlobalXml(context);
+            config = getConfigFromGlobalXml(context, flags);
         } else {
-            config = getConfigFromPmValues(context);
+            config = getConfigFromPmValues(context, flags);
         }
         return config;
     }
 
     private static DisplayDeviceConfig createWithoutDefaultValues(Context context,
-            long physicalDisplayId, boolean isFirstDisplay) {
+            long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
         DisplayDeviceConfig config;
 
         config = loadConfigFromDirectory(context, Environment.getProductDirectory(),
-                physicalDisplayId);
+                physicalDisplayId, flags);
         if (config != null) {
             return config;
         }
 
         config = loadConfigFromDirectory(context, Environment.getVendorDirectory(),
-                physicalDisplayId);
+                physicalDisplayId, flags);
         if (config != null) {
             return config;
         }
@@ -907,7 +925,7 @@
         // If no config can be loaded from any ddc xml at all,
         // prepare a whole config using the global config.xml.
         // Guaranteed not null
-        return create(context, isFirstDisplay);
+        return create(context, isFirstDisplay, flags);
     }
 
     private static DisplayConfiguration loadDefaultConfigurationXml(Context context) {
@@ -960,18 +978,19 @@
     }
 
     private static DisplayDeviceConfig loadConfigFromDirectory(Context context,
-            File baseDirectory, long physicalDisplayId) {
+            File baseDirectory, long physicalDisplayId, DisplayManagerFlags flags) {
         DisplayDeviceConfig config;
         // Create config using filename from physical ID (including "stable" bit).
         config = getConfigFromSuffix(context, baseDirectory, STABLE_ID_SUFFIX_FORMAT,
-                physicalDisplayId);
+                physicalDisplayId, flags);
         if (config != null) {
             return config;
         }
 
         // Create config using filename from physical ID (excluding "stable" bit).
         final long withoutStableFlag = physicalDisplayId & ~STABLE_FLAG;
-        config = getConfigFromSuffix(context, baseDirectory, NO_SUFFIX_FORMAT, withoutStableFlag);
+        config = getConfigFromSuffix(context, baseDirectory, NO_SUFFIX_FORMAT, withoutStableFlag,
+                flags);
         if (config != null) {
             return config;
         }
@@ -980,7 +999,7 @@
         final DisplayAddress.Physical physicalAddress =
                 DisplayAddress.fromPhysicalDisplayId(physicalDisplayId);
         int port = physicalAddress.getPort();
-        config = getConfigFromSuffix(context, baseDirectory, PORT_SUFFIX_FORMAT, port);
+        config = getConfigFromSuffix(context, baseDirectory, PORT_SUFFIX_FORMAT, port, flags);
         return config;
     }
 
@@ -1599,6 +1618,13 @@
     }
 
     /**
+     * @return Auto brightness brightening levels
+     */
+    public float[] getAutoBrightnessBrighteningLevels() {
+        return mBrightnessLevels;
+    }
+
+    /**
      * @return Default peak refresh rate of the associated display
      */
     public int getDefaultPeakRefreshRate() {
@@ -1741,6 +1767,13 @@
         return mHostUsiVersion;
     }
 
+    /**
+     * @return Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode.
+     */
+    public float getBrightnessCapForWearBedtimeMode() {
+        return mBrightnessCapForWearBedtimeMode;
+    }
+
     @Override
     public String toString() {
         return "DisplayDeviceConfig{"
@@ -1844,6 +1877,7 @@
                 + mAutoBrightnessDarkeningLightDebounceIdle
                 + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
                 + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+                + ", mBrightnessLevels= " + Arrays.toString(mBrightnessLevels)
                 + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
                 + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
                 + "\n"
@@ -1867,36 +1901,39 @@
                 + ", mHighAmbientBrightnessThresholds= "
                 + Arrays.toString(mHighAmbientBrightnessThresholds)
                 + "\n"
-                + "mScreenOffBrightnessSensorValueToLux=" + Arrays.toString(
+                + "mScreenOffBrightnessSensorValueToLux= " + Arrays.toString(
                 mScreenOffBrightnessSensorValueToLux)
                 + "\n"
                 + "mUsiVersion= " + mHostUsiVersion + "\n"
-                + "mHdrBrightnessData" + mHdrBrightnessData
+                + "mHdrBrightnessData= " + mHdrBrightnessData + "\n"
+                + "mBrightnessCapForWearBedtimeMode= " + mBrightnessCapForWearBedtimeMode
                 + "}";
     }
 
     private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory,
-            String suffixFormat, long idNumber) {
+            String suffixFormat, long idNumber, DisplayManagerFlags flags) {
 
         final String suffix = String.format(Locale.ROOT, suffixFormat, idNumber);
         final String filename = String.format(Locale.ROOT, CONFIG_FILE_FORMAT, suffix);
         final File filePath = Environment.buildPath(
                 baseDirectory, ETC_DIR, DISPLAY_CONFIG_DIR, filename);
-        final DisplayDeviceConfig config = new DisplayDeviceConfig(context);
+        final DisplayDeviceConfig config = new DisplayDeviceConfig(context, flags);
         if (config.initFromFile(filePath)) {
             return config;
         }
         return null;
     }
 
-    private static DisplayDeviceConfig getConfigFromGlobalXml(Context context) {
-        DisplayDeviceConfig config = new DisplayDeviceConfig(context);
+    private static DisplayDeviceConfig getConfigFromGlobalXml(Context context,
+            DisplayManagerFlags flags) {
+        DisplayDeviceConfig config = new DisplayDeviceConfig(context, flags);
         config.initFromGlobalXml();
         return config;
     }
 
-    private static DisplayDeviceConfig getConfigFromPmValues(Context context) {
-        DisplayDeviceConfig config = new DisplayDeviceConfig(context);
+    private static DisplayDeviceConfig getConfigFromPmValues(Context context,
+            DisplayManagerFlags flags) {
+        DisplayDeviceConfig config = new DisplayDeviceConfig(context, flags);
         config.initFromDefaultValues();
         return config;
     }
@@ -1938,6 +1975,7 @@
                 loadScreenOffBrightnessSensorValueToLuxFromDdc(config);
                 loadUsiVersion(config);
                 mHdrBrightnessData = HdrBrightnessData.loadConfig(config);
+                loadBrightnessCapForWearBedtimeMode(config);
             } else {
                 Slog.w(TAG, "DisplayDeviceConfig file is null");
             }
@@ -1961,6 +1999,7 @@
         loadAutoBrightnessConfigsFromConfigXml();
         loadAutoBrightnessAvailableFromConfigXml();
         loadRefreshRateSetting(null);
+        loadBrightnessCapForWearBedtimeModeFromConfigXml();
         mLoadedFrom = "<config.xml>";
     }
 
@@ -2599,8 +2638,23 @@
      * loading the value from the display config, and if not present, falls back to config.xml.
      */
     private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) {
-        if (autoBrightnessConfig == null
-                || autoBrightnessConfig.getDisplayBrightnessMapping() == null) {
+        if (mFlags.areAutoBrightnessModesEnabled() && autoBrightnessConfig != null
+                && autoBrightnessConfig.getLuxToBrightnessMapping() != null) {
+            LuxToBrightnessMapping mapping = autoBrightnessConfig.getLuxToBrightnessMapping();
+            final int size = mapping.getMap().getPoint().size();
+            mBrightnessLevels = new float[size];
+            mBrightnessLevelsLux = new float[size];
+            for (int i = 0; i < size; i++) {
+                float backlight = mapping.getMap().getPoint().get(i).getSecond().floatValue();
+                mBrightnessLevels[i] = mBacklightToBrightnessSpline.interpolate(backlight);
+                mBrightnessLevelsLux[i] = mapping.getMap().getPoint().get(i).getFirst()
+                        .floatValue();
+            }
+            if (size > 0 && mBrightnessLevelsLux[0] != 0) {
+                throw new IllegalArgumentException(
+                        "The first lux value in the display brightness mapping must be 0");
+            }
+        } else {
             mBrightnessLevelsNits = getFloatArray(mContext.getResources()
                     .obtainTypedArray(com.android.internal.R.array
                             .config_autoBrightnessDisplayValuesNits), PowerManager
@@ -2608,18 +2662,6 @@
             mBrightnessLevelsLux = getLuxLevels(mContext.getResources()
                     .getIntArray(com.android.internal.R.array
                             .config_autoBrightnessLevels));
-        } else {
-            final int size = autoBrightnessConfig.getDisplayBrightnessMapping()
-                    .getDisplayBrightnessPoint().size();
-            mBrightnessLevelsNits = new float[size];
-            // The first control point is implicit and always at 0 lux.
-            mBrightnessLevelsLux = new float[size + 1];
-            for (int i = 0; i < size; i++) {
-                mBrightnessLevelsNits[i] = autoBrightnessConfig.getDisplayBrightnessMapping()
-                        .getDisplayBrightnessPoint().get(i).getNits().floatValue();
-                mBrightnessLevelsLux[i + 1] = autoBrightnessConfig.getDisplayBrightnessMapping()
-                        .getDisplayBrightnessPoint().get(i).getLux().floatValue();
-            }
         }
     }
 
@@ -3350,6 +3392,23 @@
                 : null;
     }
 
+    private void loadBrightnessCapForWearBedtimeMode(DisplayConfiguration config) {
+        if (config != null) {
+            BigDecimal configBrightnessCap = config.getScreenBrightnessCapForWearBedtimeMode();
+            if (configBrightnessCap != null) {
+                mBrightnessCapForWearBedtimeMode = configBrightnessCap.floatValue();
+            } else {
+                loadBrightnessCapForWearBedtimeModeFromConfigXml();
+            }
+        }
+    }
+
+    private void loadBrightnessCapForWearBedtimeModeFromConfigXml() {
+        mBrightnessCapForWearBedtimeMode = BrightnessSynchronizer.brightnessIntToFloat(
+                mContext.getResources().getInteger(com.android.internal.R.integer
+                        .config_screenBrightnessCapForWearBedtimeMode));
+    }
+
     /**
      * Container for high brightness mode configuration data.
      */
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 6a7c17d..2ab15e6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1853,7 +1853,7 @@
             // early apps like SetupWizard/Launcher. In particular, SUW is displayed using
             // the virtual display inside VR before any VR-specific apps even run.
             mVirtualDisplayAdapter = mInjector.getVirtualDisplayAdapter(mSyncRoot, mContext,
-                    mHandler, mDisplayDeviceRepo);
+                    mHandler, mDisplayDeviceRepo, mFlags);
             if (mVirtualDisplayAdapter != null) {
                 registerDisplayAdapterLocked(mVirtualDisplayAdapter);
             }
@@ -1871,7 +1871,7 @@
 
     private void registerOverlayDisplayAdapterLocked() {
         registerDisplayAdapterLocked(new OverlayDisplayAdapter(
-                mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler));
+                mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler, mFlags));
     }
 
     private void registerWifiDisplayAdapterLocked() {
@@ -1880,7 +1880,7 @@
                 || SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) {
             mWifiDisplayAdapter = new WifiDisplayAdapter(
                     mSyncRoot, mContext, mHandler, mDisplayDeviceRepo,
-                    mPersistentDataStore);
+                    mPersistentDataStore, mFlags);
             registerDisplayAdapterLocked(mWifiDisplayAdapter);
         }
     }
@@ -3288,8 +3288,10 @@
     @VisibleForTesting
     static class Injector {
         VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
-                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
-            return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener);
+                Handler handler, DisplayAdapter.Listener displayAdapterListener,
+                DisplayManagerFlags flags) {
+            return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
+                    flags);
         }
 
         LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index ff9a1ab..22898a6 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -84,8 +84,6 @@
 
     private final boolean mIsBootDisplayModeSupported;
 
-    private final DisplayManagerFlags mFlags;
-
     private final DisplayNotificationManager mDisplayNotificationManager;
 
     private Context mOverlayContext;
@@ -103,12 +101,11 @@
             Listener listener, DisplayManagerFlags flags,
             DisplayNotificationManager displayNotificationManager,
             Injector injector) {
-        super(syncRoot, context, handler, listener, TAG);
+        super(syncRoot, context, handler, listener, TAG, flags);
         mDisplayNotificationManager = displayNotificationManager;
         mInjector = injector;
         mSurfaceControlProxy = mInjector.getSurfaceControlProxy();
         mIsBootDisplayModeSupported = mSurfaceControlProxy.getBootDisplayModeSupport();
-        mFlags = flags;
     }
 
     @Override
@@ -510,7 +507,7 @@
             // Load display device config
             final Context context = getOverlayContext();
             mDisplayDeviceConfig = mInjector.createDisplayDeviceConfig(context, mPhysicalDisplayId,
-                    mIsFirstDisplay);
+                    mIsFirstDisplay, getFeatureFlags());
 
             // Load brightness HWC quirk
             mBacklightAdapter.setForceSurfaceControl(mDisplayDeviceConfig.hasQuirk(
@@ -831,7 +828,8 @@
                                     + ", state=" + Display.stateToString(state) + ")");
                         }
 
-                        boolean isDisplayOffloadEnabled = mFlags.isDisplayOffloadEnabled();
+                        boolean isDisplayOffloadEnabled =
+                                getFeatureFlags().isDisplayOffloadEnabled();
 
                         // We must tell sidekick/displayoffload to stop controlling the display
                         // before we can change its power mode, so do that first.
@@ -1377,8 +1375,8 @@
         }
 
         public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
-                long physicalDisplayId, boolean isFirstDisplay) {
-            return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay);
+                long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
+            return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay, flags);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 2ce7690..22ff2d0 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.mode.DisplayModeDirector;
 
 import java.io.PrintWriter;
@@ -134,8 +135,9 @@
 
     // Called with SyncRoot lock held.
     public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
-            Context context, Handler handler, Listener listener, Handler uiHandler) {
-        super(syncRoot, context, handler, listener, TAG);
+            Context context, Handler handler, Listener listener, Handler uiHandler,
+            DisplayManagerFlags featureFlags) {
+        super(syncRoot, context, handler, listener, TAG, featureFlags);
         mUiHandler = uiHandler;
     }
 
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index edbd424..ec5ad7d 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -61,6 +61,7 @@
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -88,7 +89,7 @@
 
     // Called with SyncRoot lock held.
     public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
-            Context context, Handler handler, Listener listener) {
+            Context context, Handler handler, Listener listener, DisplayManagerFlags featureFlags) {
         this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() {
             @Override
             public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) {
@@ -99,14 +100,15 @@
             public void destroyDisplay(IBinder displayToken) {
                 DisplayControl.destroyDisplay(displayToken);
             }
-        });
+        }, featureFlags);
     }
 
     @VisibleForTesting
     VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
             Context context, Handler handler, Listener listener,
-            SurfaceControlDisplayFactory surfaceControlDisplayFactory) {
-        super(syncRoot, context, handler, listener, TAG);
+            SurfaceControlDisplayFactory surfaceControlDisplayFactory,
+            DisplayManagerFlags featureFlags) {
+        super(syncRoot, context, handler, listener, TAG, featureFlags);
         mHandler = handler;
         mSurfaceControlDisplayFactory = surfaceControlDisplayFactory;
     }
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 7660cf8..aa98cd8 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -41,6 +41,7 @@
 
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.utils.DebugUtils;
 
 import java.io.PrintWriter;
@@ -99,8 +100,8 @@
     // Called with SyncRoot lock held.
     public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
             Context context, Handler handler, Listener listener,
-            PersistentDataStore persistentDataStore) {
-        super(syncRoot, context, handler, listener, TAG);
+            PersistentDataStore persistentDataStore, DisplayManagerFlags featureFlags) {
+        super(syncRoot, context, handler, listener, TAG, featureFlags);
 
         if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
             throw new RuntimeException("WiFi display was requested, "
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index dfcda40..8a884b6 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -17,6 +17,7 @@
 package com.android.server.display.brightness.clamper;
 
 import android.annotation.NonNull;
+import android.os.Handler;
 import android.os.PowerManager;
 
 import com.android.server.display.DisplayBrightnessState;
@@ -31,6 +32,18 @@
     protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
     protected boolean mIsActive = false;
 
+    @NonNull
+    protected final Handler mHandler;
+
+    @NonNull
+    protected final BrightnessClamperController.ClamperChangeListener mChangeListener;
+
+    BrightnessClamper(Handler handler,
+            BrightnessClamperController.ClamperChangeListener changeListener) {
+        mHandler = handler;
+        mChangeListener = changeListener;
+    }
+
     float getBrightnessCap() {
         return mBrightnessCap;
     }
@@ -60,6 +73,7 @@
 
     protected enum Type {
         THERMAL,
-        POWER
+        POWER,
+        BEDTIME_MODE
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index b574919..765608e 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -90,7 +90,8 @@
             }
         };
 
-        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags);
+        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
+                context);
         mModifiers = injector.getModifiers(context);
         mOnPropertiesChangedListener =
                 properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
@@ -234,7 +235,7 @@
 
         List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler,
                 ClamperChangeListener clamperChangeListener, DisplayDeviceData data,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags, Context context) {
             List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();
             clampers.add(
                     new BrightnessThermalClamper(handler, clamperChangeListener, data));
@@ -242,6 +243,10 @@
                 clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
                             data));
             }
+            if (flags.isBrightnessWearBedtimeModeClamperEnabled()) {
+                clampers.add(new BrightnessWearBedtimeModeClamper(handler, context,
+                        clamperChangeListener, data));
+            }
             return clampers;
         }
 
@@ -257,7 +262,8 @@
      * Config Data for clampers
      */
     public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData,
-                BrightnessPowerClamper.PowerData {
+                BrightnessPowerClamper.PowerData,
+            BrightnessWearBedtimeModeClamper.WearBedtimeModeData {
         @NonNull
         private final String mUniqueDisplayId;
         @NonNull
@@ -315,5 +321,10 @@
         public PowerThrottlingConfigData getPowerThrottlingConfigData() {
             return mDisplayDeviceConfig.getPowerThrottlingConfigData();
         }
+
+        @Override
+        public float getBrightnessWearBedtimeModeCap() {
+            return mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
index 339b589..790322d 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
@@ -49,10 +49,6 @@
     private final Injector mInjector;
     @NonNull
     private final DeviceConfigParameterProvider mConfigParameterProvider;
-    @NonNull
-    private final Handler mHandler;
-    @NonNull
-    private final ClamperChangeListener mChangeListener;
     @Nullable
     private PmicMonitor mPmicMonitor;
     // data from DeviceConfig, for all displays, for all dataSets
@@ -99,10 +95,9 @@
     @VisibleForTesting
     BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener,
             PowerData powerData) {
+        super(handler, listener);
         mInjector = injector;
         mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
-        mHandler = handler;
-        mChangeListener = listener;
 
         mHandler.post(() -> {
             setDisplayData(powerData);
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
index 8ae962b..944a8a6 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
@@ -54,10 +54,6 @@
     private final IThermalService mThermalService;
     @NonNull
     private final DeviceConfigParameterProvider mConfigParameterProvider;
-    @NonNull
-    private final Handler mHandler;
-    @NonNull
-    private final ClamperChangeListener mChangelistener;
     // data from DeviceConfig, for all displays, for all dataSets
     // mapOf(uniqueDisplayId to mapOf(dataSetId to ThermalBrightnessThrottlingData))
     @NonNull
@@ -108,10 +104,9 @@
     @VisibleForTesting
     BrightnessThermalClamper(Injector injector, Handler handler,
             ClamperChangeListener listener, ThermalData thermalData) {
+        super(handler, listener);
         mThermalService = injector.getThermalService();
         mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
-        mHandler = handler;
-        mChangelistener = listener;
         mHandler.post(() -> {
             setDisplayData(thermalData);
             loadOverrideData();
@@ -220,7 +215,7 @@
         if (brightnessCap  != mBrightnessCap || mIsActive != isActive) {
             mBrightnessCap = brightnessCap;
             mIsActive = isActive;
-            mChangelistener.onChanged();
+            mChangeListener.onChanged();
         }
     }
 
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
new file mode 100644
index 0000000..7e853bf
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
@@ -0,0 +1,99 @@
+/*
+ * 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.display.brightness.clamper;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+public class BrightnessWearBedtimeModeClamper extends
+        BrightnessClamper<BrightnessWearBedtimeModeClamper.WearBedtimeModeData> {
+
+    public static final int BEDTIME_MODE_OFF = 0;
+    public static final int BEDTIME_MODE_ON = 1;
+
+    private final Context mContext;
+
+    private final ContentObserver mSettingsObserver;
+
+    BrightnessWearBedtimeModeClamper(Handler handler, Context context,
+            BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) {
+        this(new Injector(), handler, context, listener, data);
+    }
+
+    @VisibleForTesting
+    BrightnessWearBedtimeModeClamper(Injector injector, Handler handler, Context context,
+            BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) {
+        super(handler, listener);
+        mContext = context;
+        mBrightnessCap = data.getBrightnessWearBedtimeModeCap();
+        mSettingsObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                final int bedtimeModeSetting = Settings.Global.getInt(
+                        mContext.getContentResolver(),
+                        Settings.Global.Wearable.BEDTIME_MODE,
+                        BEDTIME_MODE_OFF);
+                mIsActive = bedtimeModeSetting == BEDTIME_MODE_ON;
+                mChangeListener.onChanged();
+            }
+        };
+        injector.registerBedtimeModeObserver(context.getContentResolver(), mSettingsObserver);
+    }
+
+    @NonNull
+    @Override
+    Type getType() {
+        return Type.BEDTIME_MODE;
+    }
+
+    @Override
+    void onDeviceConfigChanged() {}
+
+    @Override
+    void onDisplayChanged(WearBedtimeModeData displayData) {
+        mHandler.post(() -> {
+            mBrightnessCap = displayData.getBrightnessWearBedtimeModeCap();
+            mChangeListener.onChanged();
+        });
+    }
+
+    @Override
+    void stop() {
+        mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
+    }
+
+    interface WearBedtimeModeData {
+        float getBrightnessWearBedtimeModeCap();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        void registerBedtimeModeObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            cr.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE),
+                    /* notifyForDescendants= */ false, observer, UserHandle.USER_ALL);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 5cfbf26..c71f0cf2 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -90,6 +90,14 @@
             Flags.FLAG_ENABLE_EXTERNAL_VSYNC_PROXIMITY_VOTE,
             Flags::enableExternalVsyncProximityVote);
 
+    private final FlagState mBrightnessWearBedtimeModeClamperFlagState = new FlagState(
+            Flags.FLAG_BRIGHTNESS_WEAR_BEDTIME_MODE_CLAMPER,
+            Flags::brightnessWearBedtimeModeClamper);
+
+    private final FlagState mAutoBrightnessModesFlagState = new FlagState(
+            Flags.FLAG_AUTO_BRIGHTNESS_MODES,
+            Flags::autoBrightnessModes);
+
     /** Returns whether connected display management is enabled or not. */
     public boolean isConnectedDisplayManagementEnabled() {
         return mConnectedDisplayManagementFlagState.isEnabled();
@@ -178,6 +186,17 @@
         return mVsyncProximityVote.isEnabled();
     }
 
+    public boolean isBrightnessWearBedtimeModeClamperEnabled() {
+        return mBrightnessWearBedtimeModeClamperFlagState.isEnabled();
+    }
+
+    /**
+     * @return Whether generic auto-brightness modes are enabled
+     */
+    public boolean areAutoBrightnessModesEnabled() {
+        return mAutoBrightnessModesFlagState.isEnabled();
+    }
+
     /**
      * dumps all flagstates
      * @param pw printWriter
@@ -197,6 +216,8 @@
         pw.println(" " + mSmallAreaDetectionFlagState);
         pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState);
         pw.println(" " + mVsyncProximityVote);
+        pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState);
+        pw.println(" " + mAutoBrightnessModesFlagState);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index d95bdae..9dfa1ee 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -120,3 +120,19 @@
     bug: "284866750"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "brightness_wear_bedtime_mode_clamper"
+    namespace: "display_manager"
+    description: "Feature flag for the Wear Bedtime mode brightness clamper"
+    bug: "293613040"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "auto_brightness_modes"
+    namespace: "display_manager"
+    description: "Feature flag for generic auto-brightness modes"
+    bug: "293613040"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 8eb03ec..8fa838d 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -21,8 +21,8 @@
 import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT;
 import static android.view.Display.Mode.INVALID_MODE_ID;
 
-import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRate;
 import static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE;
+import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay;
 
 import android.annotation.IntegerRes;
 import android.annotation.NonNull;
@@ -925,16 +925,11 @@
     }
 
     @VisibleForTesting
-    DisplayObserver getDisplayObserver() {
-        return mDisplayObserver;
-    }
-
-    @VisibleForTesting
     DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
             float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
         synchronized (mLock) {
-            mSettingsObserver.updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate,
-                    defaultRefreshRate, Display.DEFAULT_DISPLAY);
+            mSettingsObserver.updateRefreshRateSettingLocked(
+                    minRefreshRate, peakRefreshRate, defaultRefreshRate);
             return getDesiredDisplayModeSpecs(Display.DEFAULT_DISPLAY);
         }
     }
@@ -1268,23 +1263,9 @@
             mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode);
         }
 
-        /**
-         * Update refresh rate settings for all displays
-         */
         private void updateRefreshRateSettingLocked() {
-            Display[] displays = mInjector.getDisplays();
-            for (int i = 0; i < displays.length; i++) {
-                updateRefreshRateSettingLocked(displays[i].getDisplayId());
-            }
-        }
-
-        /**
-         * Update refresh rate settings for a specific display
-         * @param displayId The display ID
-         */
-        private void updateRefreshRateSettingLocked(int displayId) {
             final ContentResolver cr = mContext.getContentResolver();
-            float highestRefreshRate = findHighestRefreshRate(mContext, displayId);
+            float highestRefreshRate = findHighestRefreshRateForDefaultDisplay(mContext);
 
             float minRefreshRate = Settings.System.getFloatForUser(cr,
                     Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId());
@@ -1312,12 +1293,11 @@
                 }
             }
 
-            updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate,
-                    displayId);
+            updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
         }
 
-        private void updateRefreshRateSettingLocked(float minRefreshRate, float peakRefreshRate,
-                float defaultRefreshRate, int displayId) {
+        private void updateRefreshRateSettingLocked(
+                float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
             // TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is
             // used to predict if we're going to be doing frequent refresh rate switching, and if
             // so, enable the brightness observer. The logic here is more complicated and fragile
@@ -1325,9 +1305,9 @@
             Vote peakVote = peakRefreshRate == 0f
                     ? null
                     : Vote.forRenderFrameRates(0f, Math.max(minRefreshRate, peakRefreshRate));
-            mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
                     peakVote);
-            mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+            mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
                     Vote.forRenderFrameRates(minRefreshRate, Float.POSITIVE_INFINITY));
             Vote defaultVote =
                     defaultRefreshRate == 0f
@@ -1484,8 +1464,7 @@
         }
     }
 
-    @VisibleForTesting
-    public final class DisplayObserver implements DisplayManager.DisplayListener {
+    private final class DisplayObserver implements DisplayManager.DisplayListener {
         // Note that we can never call into DisplayManager or any of the non-POD classes it
         // returns, while holding mLock since it may call into DMS, which might be simultaneously
         // calling into us already holding its own lock.
@@ -1577,7 +1556,6 @@
             updateDisplayModes(displayId, displayInfo);
             updateLayoutLimitedFrameRate(displayId, displayInfo);
             updateUserSettingDisplayPreferredSize(displayInfo);
-            mSettingsObserver.updateRefreshRateSettingLocked(displayId);
         }
 
         @Nullable
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 6f9b7d6..27b01a5 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -67,6 +67,7 @@
 import android.location.LocationManager;
 import android.location.LocationRequest;
 import android.location.LocationResult;
+import android.location.flags.Flags;
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
@@ -1048,10 +1049,22 @@
                 stopBatching();
 
                 if (mStarted && mGnssNative.getCapabilities().hasScheduling()) {
-                    // change period and/or lowPowerMode
-                    if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
-                            mFixInterval, mProviderRequest.isLowPower())) {
-                        Log.e(TAG, "set_position_mode failed in updateRequirements");
+                    if (Flags.gnssCallStopBeforeSetPositionMode()) {
+                        GnssPositionMode positionMode = new GnssPositionMode(mPositionMode,
+                                GNSS_POSITION_RECURRENCE_PERIODIC, mFixInterval,
+                                /* preferredAccuracy= */ 0,
+                                /* preferredTime= */ 0,
+                                mProviderRequest.isLowPower());
+                        if (!positionMode.equals(mLastPositionMode)) {
+                            stopNavigating();
+                            startNavigating();
+                        }
+                    } else {
+                        // change period and/or lowPowerMode
+                        if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
+                                mFixInterval, mProviderRequest.isLowPower())) {
+                            Log.e(TAG, "set_position_mode failed in updateRequirements");
+                        }
                     }
                 } else if (!mStarted) {
                     // start GPS
@@ -1234,11 +1247,32 @@
             }
 
             int interval = mGnssNative.getCapabilities().hasScheduling() ? mFixInterval : 1000;
-            if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
-                    interval, mProviderRequest.isLowPower())) {
-                setStarted(false);
-                Log.e(TAG, "set_position_mode failed in startNavigating()");
-                return;
+
+            if (Flags.gnssCallStopBeforeSetPositionMode()) {
+                boolean success = mGnssNative.setPositionMode(mPositionMode,
+                        GNSS_POSITION_RECURRENCE_PERIODIC, interval,
+                        /* preferredAccuracy= */ 0,
+                        /* preferredTime= */ 0,
+                        mProviderRequest.isLowPower());
+                if (success) {
+                    mLastPositionMode = new GnssPositionMode(mPositionMode,
+                            GNSS_POSITION_RECURRENCE_PERIODIC, interval,
+                            /* preferredAccuracy= */ 0,
+                            /* preferredTime= */ 0,
+                            mProviderRequest.isLowPower());
+                } else {
+                    mLastPositionMode = null;
+                    setStarted(false);
+                    Log.e(TAG, "set_position_mode failed in startNavigating()");
+                    return;
+                }
+            } else {
+                if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
+                        interval, mProviderRequest.isLowPower())) {
+                    setStarted(false);
+                    Log.e(TAG, "set_position_mode failed in startNavigating()");
+                    return;
+                }
             }
             if (!mGnssNative.start()) {
                 setStarted(false);
diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
deleted file mode 100644
index 8cb334d..0000000
--- a/services/core/java/com/android/server/media/AudioAttributesUtils.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.media.AudioAttributes;
-import android.media.AudioDeviceAttributes;
-import android.media.AudioDeviceInfo;
-import android.media.MediaRoute2Info;
-
-import com.android.media.flags.Flags;
-
-/* package */ final class AudioAttributesUtils {
-
-    /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_MEDIA)
-            .build();
-
-    private AudioAttributesUtils() {
-        // no-op to prevent instantiation.
-    }
-
-    @MediaRoute2Info.Type
-    /* package */ static int mapToMediaRouteType(
-            @NonNull AudioDeviceAttributes audioDeviceAttributes) {
-        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
-            switch (audioDeviceAttributes.getType()) {
-                case AudioDeviceInfo.TYPE_HDMI_ARC:
-                    return MediaRoute2Info.TYPE_HDMI_ARC;
-                case AudioDeviceInfo.TYPE_HDMI_EARC:
-                    return MediaRoute2Info.TYPE_HDMI_EARC;
-            }
-        }
-        switch (audioDeviceAttributes.getType()) {
-            case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
-            case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
-                return MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
-            case AudioDeviceInfo.TYPE_WIRED_HEADSET:
-                return MediaRoute2Info.TYPE_WIRED_HEADSET;
-            case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
-                return MediaRoute2Info.TYPE_WIRED_HEADPHONES;
-            case AudioDeviceInfo.TYPE_DOCK:
-            case AudioDeviceInfo.TYPE_DOCK_ANALOG:
-                return MediaRoute2Info.TYPE_DOCK;
-            case AudioDeviceInfo.TYPE_HDMI:
-            case AudioDeviceInfo.TYPE_HDMI_ARC:
-            case AudioDeviceInfo.TYPE_HDMI_EARC:
-                return MediaRoute2Info.TYPE_HDMI;
-            case AudioDeviceInfo.TYPE_USB_DEVICE:
-                return MediaRoute2Info.TYPE_USB_DEVICE;
-            case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
-                return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
-            case AudioDeviceInfo.TYPE_BLE_HEADSET:
-                return MediaRoute2Info.TYPE_BLE_HEADSET;
-            case AudioDeviceInfo.TYPE_HEARING_AID:
-                return MediaRoute2Info.TYPE_HEARING_AID;
-            default:
-                return MediaRoute2Info.TYPE_UNKNOWN;
-        }
-    }
-
-    /* package */ static boolean isDeviceOutputAttributes(
-            @Nullable AudioDeviceAttributes audioDeviceAttributes) {
-        if (audioDeviceAttributes == null) {
-            return false;
-        }
-
-        if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
-            return false;
-        }
-
-        switch (audioDeviceAttributes.getType()) {
-            case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
-            case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
-            case AudioDeviceInfo.TYPE_WIRED_HEADSET:
-            case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
-            case AudioDeviceInfo.TYPE_DOCK:
-            case AudioDeviceInfo.TYPE_DOCK_ANALOG:
-            case AudioDeviceInfo.TYPE_HDMI:
-            case AudioDeviceInfo.TYPE_HDMI_ARC:
-            case AudioDeviceInfo.TYPE_HDMI_EARC:
-            case AudioDeviceInfo.TYPE_USB_DEVICE:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    /* package */ static boolean isBluetoothOutputAttributes(
-            @Nullable AudioDeviceAttributes audioDeviceAttributes) {
-        if (audioDeviceAttributes == null) {
-            return false;
-        }
-
-        if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
-            return false;
-        }
-
-        switch (audioDeviceAttributes.getType()) {
-            case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
-            case AudioDeviceInfo.TYPE_BLE_HEADSET:
-            case AudioDeviceInfo.TYPE_BLE_SPEAKER:
-            case AudioDeviceInfo.TYPE_HEARING_AID:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-}
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
index 8bc69c2..a00999d 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -17,7 +17,6 @@
 package com.android.server.media;
 
 import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO;
-import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,38 +30,37 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.media.AudioManager;
-import android.media.AudioSystem;
 import android.media.MediaRoute2Info;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
- * Controls bluetooth routes and provides selected route override.
+ * Maintains a list of connected {@link BluetoothDevice bluetooth devices} and allows their
+ * activation.
  *
- * <p>The controller offers similar functionality to {@link LegacyBluetoothRouteController} but does
- * not support routes selection logic. Instead, relies on external clients to make a decision
- * about currently selected route.
- *
- * <p>Selected route override should be used by {@link AudioManager} which is aware of Audio
- * Policies.
+ * <p>This class also serves as ground truth for assigning {@link MediaRoute2Info#getId() route ids}
+ * for bluetooth routes via {@link #getRouteIdForBluetoothAddress}.
  */
-/* package */ class AudioPoliciesBluetoothRouteController
-        implements BluetoothRouteController {
-    private static final String TAG = "APBtRouteController";
+// TODO: b/305199571 - Rename this class to remove the RouteController suffix, which causes
+// confusion with the BluetoothRouteController interface.
+/* package */ class AudioPoliciesBluetoothRouteController {
+    private static final String TAG = SystemMediaRoute2Provider.TAG;
 
     private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
     private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_";
@@ -75,11 +73,8 @@
     private final DeviceStateChangedReceiver mDeviceStateChangedReceiver =
             new DeviceStateChangedReceiver();
 
-    @NonNull
-    private final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
-
-    @NonNull
-    private final SparseIntArray mVolumeMap = new SparseIntArray();
+    @NonNull private Map<String, BluetoothDevice> mAddressToBondedDevice = new HashMap<>();
+    @NonNull private final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
 
     @NonNull
     private final Context mContext;
@@ -89,11 +84,6 @@
     private final BluetoothRouteController.BluetoothRoutesUpdatedListener mListener;
     @NonNull
     private final BluetoothProfileMonitor mBluetoothProfileMonitor;
-    @NonNull
-    private final AudioManager mAudioManager;
-
-    @Nullable
-    private BluetoothRouteInfo mSelectedBluetoothRoute;
 
     AudioPoliciesBluetoothRouteController(@NonNull Context context,
             @NonNull BluetoothAdapter bluetoothAdapter,
@@ -107,21 +97,12 @@
             @NonNull BluetoothAdapter bluetoothAdapter,
             @NonNull BluetoothProfileMonitor bluetoothProfileMonitor,
             @NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
-        Objects.requireNonNull(context);
-        Objects.requireNonNull(bluetoothAdapter);
-        Objects.requireNonNull(bluetoothProfileMonitor);
-        Objects.requireNonNull(listener);
-
-        mContext = context;
-        mBluetoothAdapter = bluetoothAdapter;
-        mBluetoothProfileMonitor = bluetoothProfileMonitor;
-        mAudioManager = mContext.getSystemService(AudioManager.class);
-        mListener = listener;
-
-        updateBluetoothRoutes();
+        mContext = Objects.requireNonNull(context);
+        mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
+        mBluetoothProfileMonitor = Objects.requireNonNull(bluetoothProfileMonitor);
+        mListener = Objects.requireNonNull(listener);
     }
 
-    @Override
     public void start(UserHandle user) {
         mBluetoothProfileMonitor.start();
 
@@ -133,122 +114,63 @@
 
         IntentFilter deviceStateChangedIntentFilter = new IntentFilter();
 
-        deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
         deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         deviceStateChangedIntentFilter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
         deviceStateChangedIntentFilter.addAction(
                 BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
         deviceStateChangedIntentFilter.addAction(
                 BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
-        deviceStateChangedIntentFilter.addAction(
-                BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
 
         mContext.registerReceiverAsUser(mDeviceStateChangedReceiver, user,
                 deviceStateChangedIntentFilter, null, null);
+        updateBluetoothRoutes();
     }
 
-    @Override
     public void stop() {
         mContext.unregisterReceiver(mAdapterStateChangedReceiver);
         mContext.unregisterReceiver(mDeviceStateChangedReceiver);
     }
 
-    @Override
-    public boolean selectRoute(@Nullable String deviceAddress) {
-        synchronized (this) {
-            // Fetch all available devices in order to avoid race conditions with Bluetooth stack.
-            updateBluetoothRoutes();
-
-            if (deviceAddress == null) {
-                mSelectedBluetoothRoute = null;
-                return true;
-            }
-
-            BluetoothRouteInfo bluetoothRouteInfo = mBluetoothRoutes.get(deviceAddress);
-
-            if (bluetoothRouteInfo == null) {
-                Slog.w(TAG, "Cannot find bluetooth route for " + deviceAddress);
-                return false;
-            }
-
-            mSelectedBluetoothRoute = bluetoothRouteInfo;
-            setRouteConnectionState(mSelectedBluetoothRoute, STATE_CONNECTED);
-
-            updateConnectivityStateForDevicesInTheSameGroup();
-
-            return true;
-        }
+    @Nullable
+    public synchronized String getRouteIdForBluetoothAddress(@Nullable String address) {
+        BluetoothDevice bluetoothDevice = mAddressToBondedDevice.get(address);
+        // TODO: b/305199571 - Optimize the following statement to avoid creating the full
+        // MediaRoute2Info instance. We just need the id.
+        return bluetoothDevice != null
+                ? createBluetoothRoute(bluetoothDevice).mRoute.getId()
+                : null;
     }
 
-    /**
-     * Updates connectivity state for devices in the same devices group.
-     *
-     * <p>{@link BluetoothProfile#LE_AUDIO} and {@link BluetoothProfile#HEARING_AID} support
-     * grouping devices. Devices that belong to the same group should have the same routeId but
-     * different physical address.
-     *
-     * <p>In case one of the devices from the group is selected then other devices should also
-     * reflect this by changing their connectivity status to
-     * {@link MediaRoute2Info#CONNECTION_STATE_CONNECTED}.
-     */
-    private void updateConnectivityStateForDevicesInTheSameGroup() {
-        synchronized (this) {
-            for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
-                if (TextUtils.equals(btRoute.mRoute.getId(), mSelectedBluetoothRoute.mRoute.getId())
-                        && !TextUtils.equals(btRoute.mBtDevice.getAddress(),
-                        mSelectedBluetoothRoute.mBtDevice.getAddress())) {
-                    setRouteConnectionState(btRoute, STATE_CONNECTED);
-                }
-            }
-        }
-    }
-
-    @Override
-    public void transferTo(@Nullable String routeId) {
-        if (routeId == null) {
-            mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_AUDIO);
-            return;
-        }
-
-        BluetoothRouteInfo btRouteInfo = findBluetoothRouteWithRouteId(routeId);
+    public synchronized void activateBluetoothDeviceWithAddress(String address) {
+        BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(address);
 
         if (btRouteInfo == null) {
-            Slog.w(TAG, "transferTo: Unknown route. ID=" + routeId);
+            Slog.w(TAG, "activateBluetoothDeviceWithAddress: Ignoring unknown address " + address);
             return;
         }
-
         mBluetoothAdapter.setActiveDevice(btRouteInfo.mBtDevice, ACTIVE_DEVICE_AUDIO);
     }
 
-    @Nullable
-    private BluetoothRouteInfo findBluetoothRouteWithRouteId(@Nullable String routeId) {
-        if (routeId == null) {
-            return null;
-        }
-        synchronized (this) {
-            for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) {
-                if (TextUtils.equals(btRouteInfo.mRoute.getId(), routeId)) {
-                    return btRouteInfo;
-                }
-            }
-        }
-        return null;
-    }
-
     private void updateBluetoothRoutes() {
         Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
 
-        if (bondedDevices == null) {
-            return;
-        }
-
         synchronized (this) {
             mBluetoothRoutes.clear();
-
-            // We need to query all available to BT stack devices in order to avoid inconsistency
-            // between external services, like, AndroidManager, and BT stack.
+            if (bondedDevices == null) {
+                // Bonded devices is null upon running into a BluetoothAdapter error.
+                Log.w(TAG, "BluetoothAdapter.getBondedDevices returned null.");
+                return;
+            }
+            // We don't clear bonded devices if we receive a null getBondedDevices result, because
+            // that probably means that the bluetooth stack ran into an issue. Not that all devices
+            // have been unpaired.
+            mAddressToBondedDevice =
+                    bondedDevices.stream()
+                            .collect(
+                                    Collectors.toMap(
+                                            BluetoothDevice::getAddress, Function.identity()));
             for (BluetoothDevice device : bondedDevices) {
-                if (isDeviceConnected(device)) {
+                if (device.isConnected()) {
                     BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
                     if (newBtRoute.mConnectedProfiles.size() > 0) {
                         mBluetoothRoutes.put(device.getAddress(), newBtRoute);
@@ -258,106 +180,51 @@
         }
     }
 
-    @VisibleForTesting
-        /* package */ boolean isDeviceConnected(@NonNull BluetoothDevice device) {
-        return device.isConnected();
-    }
-
-    @Nullable
-    @Override
-    public MediaRoute2Info getSelectedRoute() {
-        synchronized (this) {
-            if (mSelectedBluetoothRoute == null) {
-                return null;
-            }
-
-            return mSelectedBluetoothRoute.mRoute;
-        }
-    }
-
     @NonNull
-    @Override
-    public List<MediaRoute2Info> getTransferableRoutes() {
-        List<MediaRoute2Info> routes = getAllBluetoothRoutes();
-        synchronized (this) {
-            if (mSelectedBluetoothRoute != null) {
-                routes.remove(mSelectedBluetoothRoute.mRoute);
-            }
-        }
-        return routes;
-    }
-
-    @NonNull
-    @Override
-    public List<MediaRoute2Info> getAllBluetoothRoutes() {
+    public List<MediaRoute2Info> getAvailableBluetoothRoutes() {
         List<MediaRoute2Info> routes = new ArrayList<>();
-        List<String> routeIds = new ArrayList<>();
-
-        MediaRoute2Info selectedRoute = getSelectedRoute();
-        if (selectedRoute != null) {
-            routes.add(selectedRoute);
-            routeIds.add(selectedRoute.getId());
-        }
+        Set<String> routeIds = new HashSet<>();
 
         synchronized (this) {
             for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
-                // A pair of hearing aid devices or having the same hardware address
-                if (routeIds.contains(btRoute.mRoute.getId())) {
-                    continue;
+                // See createBluetoothRoute for info on why we do this.
+                if (routeIds.add(btRoute.mRoute.getId())) {
+                    routes.add(btRoute.mRoute);
                 }
-                routes.add(btRoute.mRoute);
-                routeIds.add(btRoute.mRoute.getId());
             }
         }
         return routes;
     }
 
-    @Override
-    public boolean updateVolumeForDevices(int devices, int volume) {
-        int routeType;
-        if ((devices & (AudioSystem.DEVICE_OUT_HEARING_AID)) != 0) {
-            routeType = MediaRoute2Info.TYPE_HEARING_AID;
-        } else if ((devices & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP
-                | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES
-                | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
-            routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
-        } else if ((devices & (AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0) {
-            routeType = MediaRoute2Info.TYPE_BLE_HEADSET;
-        } else {
-            return false;
-        }
-
-        synchronized (this) {
-            mVolumeMap.put(routeType, volume);
-            if (mSelectedBluetoothRoute == null
-                    || mSelectedBluetoothRoute.mRoute.getType() != routeType) {
-                return false;
-            }
-
-            mSelectedBluetoothRoute.mRoute =
-                    new MediaRoute2Info.Builder(mSelectedBluetoothRoute.mRoute)
-                            .setVolume(volume)
-                            .build();
-        }
-
-        notifyBluetoothRoutesUpdated();
-        return true;
-    }
-
     private void notifyBluetoothRoutesUpdated() {
         mListener.onBluetoothRoutesUpdated();
     }
 
+    /**
+     * Creates a new {@link BluetoothRouteInfo}, including its member {@link
+     * BluetoothRouteInfo#mRoute}.
+     *
+     * <p>The most important logic in this method is around the {@link MediaRoute2Info#getId() route
+     * id} assignment. In some cases we want to group multiple {@link BluetoothDevice bluetooth
+     * devices} as a single media route. For example, the left and right hearing aids get exposed as
+     * two different BluetoothDevice instances, but we want to show them as a single route. In this
+     * case, we assign the same route id to all "group" bluetooth devices (like left and right
+     * hearing aids), so that a single route is exposed for both of them.
+     *
+     * <p>Deduplication by id happens downstream because we need to be able to refer to all
+     * bluetooth devices individually, since the audio stack refers to a bluetooth device group by
+     * any of its member devices.
+     */
     private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
         BluetoothRouteInfo
                 newBtRoute = new BluetoothRouteInfo();
         newBtRoute.mBtDevice = device;
-
-        String routeId = device.getAddress();
         String deviceName = device.getName();
         if (TextUtils.isEmpty(deviceName)) {
             deviceName = mContext.getResources().getText(R.string.unknownName).toString();
         }
+
+        String routeId = device.getAddress();
         int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
         newBtRoute.mConnectedProfiles = new SparseBooleanArray();
         if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.A2DP, device)) {
@@ -365,7 +232,6 @@
         }
         if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.HEARING_AID, device)) {
             newBtRoute.mConnectedProfiles.put(BluetoothProfile.HEARING_AID, true);
-            // Intentionally assign the same ID for a pair of devices to publish only one of them.
             routeId = HEARING_AID_ROUTE_ID_PREFIX
                     + mBluetoothProfileMonitor.getGroupId(BluetoothProfile.HEARING_AID, device);
             type = MediaRoute2Info.TYPE_HEARING_AID;
@@ -377,66 +243,27 @@
             type = MediaRoute2Info.TYPE_BLE_HEADSET;
         }
 
-        // Current volume will be set when connected.
-        newBtRoute.mRoute = new MediaRoute2Info.Builder(routeId, deviceName)
-                .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
-                .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK)
-                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
-                .setDescription(mContext.getResources().getText(
-                        R.string.bluetooth_a2dp_audio_route_name).toString())
-                .setType(type)
-                .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
-                .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
-                .setAddress(device.getAddress())
-                .build();
+        // Note that volume is only relevant for active bluetooth routes, and those are managed via
+        // AudioManager.
+        newBtRoute.mRoute =
+                new MediaRoute2Info.Builder(routeId, deviceName)
+                        .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+                        .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK)
+                        .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
+                        .setDescription(
+                                mContext.getResources()
+                                        .getText(R.string.bluetooth_a2dp_audio_route_name)
+                                        .toString())
+                        .setType(type)
+                        .setAddress(device.getAddress())
+                        .build();
         return newBtRoute;
     }
 
-    private void setRouteConnectionState(@NonNull BluetoothRouteInfo btRoute,
-            @MediaRoute2Info.ConnectionState int state) {
-        if (btRoute == null) {
-            Slog.w(TAG, "setRouteConnectionState: route shouldn't be null");
-            return;
-        }
-        if (btRoute.mRoute.getConnectionState() == state) {
-            return;
-        }
-
-        MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.mRoute)
-                .setConnectionState(state);
-        builder.setType(btRoute.getRouteType());
-
-
-
-        if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) {
-            int currentVolume;
-            synchronized (this) {
-                currentVolume = mVolumeMap.get(btRoute.getRouteType(), 0);
-            }
-            builder.setVolume(currentVolume);
-        }
-
-        btRoute.mRoute = builder.build();
-    }
-
     private static class BluetoothRouteInfo {
         private BluetoothDevice mBtDevice;
         private MediaRoute2Info mRoute;
         private SparseBooleanArray mConnectedProfiles;
-
-        @MediaRoute2Info.Type
-        int getRouteType() {
-            // Let hearing aid profile have a priority.
-            if (mConnectedProfiles.get(BluetoothProfile.HEARING_AID, false)) {
-                return MediaRoute2Info.TYPE_HEARING_AID;
-            }
-
-            if (mConnectedProfiles.get(BluetoothProfile.LE_AUDIO, false)) {
-                return MediaRoute2Info.TYPE_BLE_HEADSET;
-            }
-
-            return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
-        }
     }
 
     private class AdapterStateChangedReceiver extends BroadcastReceiver {
@@ -468,9 +295,6 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             switch (intent.getAction()) {
-                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
-                case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
-                case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
                 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
                 case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
                 case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 6bdfae2..27df00f 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -17,228 +17,590 @@
 package com.android.server.media;
 
 import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
-import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
 import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
-import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
-import static android.media.MediaRoute2Info.TYPE_DOCK;
-import static android.media.MediaRoute2Info.TYPE_HDMI;
-import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
-import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
-import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
-import android.media.AudioRoutesInfo;
-import android.media.IAudioRoutesObserver;
-import android.media.IAudioService;
 import android.media.MediaRoute2Info;
-import android.os.RemoteException;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.media.BluetoothRouteController.NoOpBluetoothRouteController;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
+/**
+ * Maintains a list of all available routes and supports transfers to any of them.
+ *
+ * <p>This implementation is intended for use in conjunction with {@link
+ * NoOpBluetoothRouteController}, as it manages bluetooth devices directly.
+ *
+ * <p>This implementation obtains and manages all routes via {@link AudioManager}, with the
+ * exception of {@link AudioManager#handleBluetoothActiveDeviceChanged inactive bluetooth} routes
+ * which are managed by {@link AudioPoliciesBluetoothRouteController}, which depends on the
+ * bluetooth stack (for example {@link BluetoothAdapter}.
+ */
+// TODO: b/305199571 - Rename this class to avoid the AudioPolicies prefix, which has been flagged
+// by the audio team as a confusing name.
 /* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController {
-
-    private static final String TAG = "APDeviceRoutesController";
+    private static final String TAG = SystemMediaRoute2Provider.TAG;
 
     @NonNull
-    private final Context mContext;
-    @NonNull
-    private final AudioManager mAudioManager;
-    @NonNull
-    private final IAudioService mAudioService;
+    private static final AudioAttributes MEDIA_USAGE_AUDIO_ATTRIBUTES =
+            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
 
     @NonNull
-    private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
-    @NonNull
-    private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
+    private static final SparseArray<SystemRouteInfo> AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO =
+            new SparseArray<>();
 
-    private int mDeviceVolume;
+    @NonNull private final Context mContext;
+    @NonNull private final AudioManager mAudioManager;
+    @NonNull private final Handler mHandler;
+    @NonNull private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+    @NonNull private final AudioPoliciesBluetoothRouteController mBluetoothRouteController;
 
     @NonNull
-    private MediaRoute2Info mDeviceRoute;
-    @Nullable
-    private MediaRoute2Info mSelectedRoute;
+    private final Map<String, MediaRoute2InfoHolder> mRouteIdToAvailableDeviceRoutes =
+            new HashMap<>();
 
-    @VisibleForTesting
-    /* package */ AudioPoliciesDeviceRouteController(@NonNull Context context,
+    @NonNull private final AudioProductStrategy mStrategyForMedia;
+
+    @NonNull private final AudioDeviceCallback mAudioDeviceCallback = new AudioDeviceCallbackImpl();
+
+    @NonNull
+    private final AudioManager.OnDevicesForAttributesChangedListener
+            mOnDevicesForAttributesChangedListener = this::onDevicesForAttributesChangedListener;
+
+    @NonNull private MediaRoute2Info mSelectedRoute;
+
+    // TODO: b/305199571 - Support nullable btAdapter and strategyForMedia which, when null, means
+    // no support for transferring to inactive bluetooth routes and transferring to any routes
+    // respectively.
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
+    /* package */ AudioPoliciesDeviceRouteController(
+            @NonNull Context context,
             @NonNull AudioManager audioManager,
-            @NonNull IAudioService audioService,
+            @NonNull Looper looper,
+            @NonNull AudioProductStrategy strategyForMedia,
+            @NonNull BluetoothAdapter btAdapter,
             @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
-        Objects.requireNonNull(context);
-        Objects.requireNonNull(audioManager);
-        Objects.requireNonNull(audioService);
-        Objects.requireNonNull(onDeviceRouteChangedListener);
-
-        mContext = context;
-        mOnDeviceRouteChangedListener = onDeviceRouteChangedListener;
-
-        mAudioManager = audioManager;
-        mAudioService = audioService;
-
-        AudioRoutesInfo newAudioRoutes = null;
-        try {
-            newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e);
-        }
-
-        mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+        mContext = Objects.requireNonNull(context);
+        mAudioManager = Objects.requireNonNull(audioManager);
+        mHandler = new Handler(Objects.requireNonNull(looper));
+        mStrategyForMedia = Objects.requireNonNull(strategyForMedia);
+        mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener);
+        mBluetoothRouteController =
+                new AudioPoliciesBluetoothRouteController(
+                        mContext, btAdapter, this::rebuildAvailableRoutes);
+        rebuildAvailableRoutes();
     }
 
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
     @Override
-    public synchronized boolean selectRoute(@Nullable Integer type) {
-        if (type == null) {
-            mSelectedRoute = null;
-            return true;
-        }
+    public void start(UserHandle mUser) {
+        mBluetoothRouteController.start(mUser);
+        mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, mHandler);
+        mAudioManager.addOnDevicesForAttributesChangedListener(
+                AudioRoutingUtils.ATTRIBUTES_MEDIA,
+                new HandlerExecutor(mHandler),
+                mOnDevicesForAttributesChangedListener);
+    }
 
-        if (!isDeviceRouteType(type)) {
-            return false;
-        }
-
-        mSelectedRoute = createRouteFromAudioInfo(type);
-        return true;
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
+    @Override
+    public void stop() {
+        mAudioManager.removeOnDevicesForAttributesChangedListener(
+                mOnDevicesForAttributesChangedListener);
+        mAudioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
+        mBluetoothRouteController.stop();
+        mHandler.removeCallbacksAndMessages(/* token= */ null);
     }
 
     @Override
     @NonNull
     public synchronized MediaRoute2Info getSelectedRoute() {
-        if (mSelectedRoute != null) {
-            return mSelectedRoute;
-        }
-        return mDeviceRoute;
+        return mSelectedRoute;
     }
 
     @Override
+    @NonNull
+    public synchronized List<MediaRoute2Info> getAvailableRoutes() {
+        return mRouteIdToAvailableDeviceRoutes.values().stream()
+                .map(it -> it.mMediaRoute2Info)
+                .toList();
+    }
+
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Override
+    public synchronized void transferTo(@Nullable String routeId) {
+        if (routeId == null) {
+            // This should never happen: This branch should only execute when the matching bluetooth
+            // route controller is not the no-op one.
+            // TODO: b/305199571 - Make routeId non-null and remove this branch once we remove the
+            // legacy route controller implementations.
+            Slog.e(TAG, "Unexpected call to AudioPoliciesDeviceRouteController#transferTo(null)");
+            return;
+        }
+        MediaRoute2InfoHolder mediaRoute2InfoHolder = mRouteIdToAvailableDeviceRoutes.get(routeId);
+        if (mediaRoute2InfoHolder == null) {
+            Slog.w(TAG, "transferTo: Ignoring transfer request to unknown route id : " + routeId);
+            return;
+        }
+        if (mediaRoute2InfoHolder.mCorrespondsToInactiveBluetoothRoute) {
+            // By default, the last connected device is the active route so we don't need to apply a
+            // routing audio policy.
+            mBluetoothRouteController.activateBluetoothDeviceWithAddress(
+                    mediaRoute2InfoHolder.mMediaRoute2Info.getAddress());
+            mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia);
+        } else {
+            AudioDeviceAttributes attr =
+                    new AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            mediaRoute2InfoHolder.mAudioDeviceInfoType,
+                            /* address= */ ""); // This is not a BT device, hence no address needed.
+            mAudioManager.setPreferredDeviceForStrategy(mStrategyForMedia, attr);
+        }
+    }
+
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
+    @Override
     public synchronized boolean updateVolume(int volume) {
-        if (mDeviceVolume == volume) {
-            return false;
-        }
-
-        mDeviceVolume = volume;
-
-        if (mSelectedRoute != null) {
-            mSelectedRoute = new MediaRoute2Info.Builder(mSelectedRoute)
-                    .setVolume(volume)
-                    .build();
-        }
-
-        mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
-                .setVolume(volume)
-                .build();
-
+        // TODO: b/305199571 - Optimize so that we only update the volume of the selected route. We
+        // don't need to rebuild all available routes.
+        rebuildAvailableRoutes();
         return true;
     }
 
-    @NonNull
-    private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) {
-        int type = TYPE_BUILTIN_SPEAKER;
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
+    private void onDevicesForAttributesChangedListener(
+            AudioAttributes attributes, List<AudioDeviceAttributes> unusedAudioDeviceAttributes) {
+        if (attributes.getUsage() == AudioAttributes.USAGE_MEDIA) {
+            // We only care about the media usage. Ignore everything else.
+            rebuildAvailableRoutes();
+        }
+    }
 
-        if (newRoutes != null) {
-            if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
-                type = TYPE_WIRED_HEADPHONES;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
-                type = TYPE_WIRED_HEADSET;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
-                type = TYPE_DOCK;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
-                type = TYPE_HDMI;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
-                type = TYPE_USB_DEVICE;
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE
+            })
+    private synchronized void rebuildAvailableRoutes() {
+        List<AudioDeviceAttributes> attributesOfSelectedOutputDevices =
+                mAudioManager.getDevicesForAttributes(MEDIA_USAGE_AUDIO_ATTRIBUTES);
+        int selectedDeviceAttributesType;
+        if (attributesOfSelectedOutputDevices.isEmpty()) {
+            Slog.e(
+                    TAG,
+                    "Unexpected empty list of output devices for media. Using built-in speakers.");
+            selectedDeviceAttributesType = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+        } else {
+            if (attributesOfSelectedOutputDevices.size() > 1) {
+                Slog.w(
+                        TAG,
+                        "AudioManager.getDevicesForAttributes returned more than one element. Using"
+                                + " the first one.");
+            }
+            selectedDeviceAttributesType = attributesOfSelectedOutputDevices.get(0).getType();
+        }
+
+        AudioDeviceInfo[] audioDeviceInfos =
+                mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        mRouteIdToAvailableDeviceRoutes.clear();
+        MediaRoute2InfoHolder newSelectedRouteHolder = null;
+        for (AudioDeviceInfo audioDeviceInfo : audioDeviceInfos) {
+            MediaRoute2Info mediaRoute2Info =
+                    createMediaRoute2InfoFromAudioDeviceInfo(audioDeviceInfo);
+            // Null means audioDeviceInfo is not a supported media output, like a phone's builtin
+            // earpiece. We ignore those.
+            if (mediaRoute2Info != null) {
+                int audioDeviceInfoType = audioDeviceInfo.getType();
+                MediaRoute2InfoHolder newHolder =
+                        MediaRoute2InfoHolder.createForAudioManagerRoute(
+                                mediaRoute2Info, audioDeviceInfoType);
+                mRouteIdToAvailableDeviceRoutes.put(mediaRoute2Info.getId(), newHolder);
+                if (selectedDeviceAttributesType == audioDeviceInfoType) {
+                    newSelectedRouteHolder = newHolder;
+                }
             }
         }
 
-        return createRouteFromAudioInfo(type);
-    }
-
-    @NonNull
-    private MediaRoute2Info createRouteFromAudioInfo(@MediaRoute2Info.Type int type) {
-        int name = R.string.default_audio_route_name;
-        switch (type) {
-            case TYPE_WIRED_HEADPHONES:
-            case TYPE_WIRED_HEADSET:
-                name = R.string.default_audio_route_name_headphones;
-                break;
-            case TYPE_DOCK:
-                name = R.string.default_audio_route_name_dock_speakers;
-                break;
-            case TYPE_HDMI:
-            case TYPE_HDMI_ARC:
-            case TYPE_HDMI_EARC:
-                name = R.string.default_audio_route_name_external_device;
-                break;
-            case TYPE_USB_DEVICE:
-                name = R.string.default_audio_route_name_usb;
-                break;
+        if (mRouteIdToAvailableDeviceRoutes.isEmpty()) {
+            // Due to an unknown reason (possibly an audio server crash), we ended up with an empty
+            // list of routes. Our entire codebase assumes at least one system route always exists,
+            // so we create a placeholder route represented as a built-in speaker for
+            // user-presentation purposes.
+            Slog.e(TAG, "Ended up with an empty list of routes. Creating a placeholder route.");
+            MediaRoute2InfoHolder placeholderRouteHolder = createPlaceholderBuiltinSpeakerRoute();
+            String placeholderRouteId = placeholderRouteHolder.mMediaRoute2Info.getId();
+            mRouteIdToAvailableDeviceRoutes.put(placeholderRouteId, placeholderRouteHolder);
         }
 
-        synchronized (this) {
-            return new MediaRoute2Info.Builder(
-                            MediaRoute2Info.ROUTE_ID_DEVICE,
-                            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();
+        if (newSelectedRouteHolder == null) {
+            Slog.e(
+                    TAG,
+                    "Could not map this selected device attribute type to an available route: "
+                            + selectedDeviceAttributesType);
+            // We know mRouteIdToAvailableDeviceRoutes is not empty.
+            newSelectedRouteHolder = mRouteIdToAvailableDeviceRoutes.values().iterator().next();
+        }
+        MediaRoute2InfoHolder selectedRouteHolderWithUpdatedVolumeInfo =
+                newSelectedRouteHolder.copyWithVolumeInfoFromAudioManager(mAudioManager);
+        mRouteIdToAvailableDeviceRoutes.put(
+                newSelectedRouteHolder.mMediaRoute2Info.getId(),
+                selectedRouteHolderWithUpdatedVolumeInfo);
+        mSelectedRoute = selectedRouteHolderWithUpdatedVolumeInfo.mMediaRoute2Info;
+
+        // We only add those BT routes that we have not already obtained from audio manager (which
+        // are active).
+        mBluetoothRouteController.getAvailableBluetoothRoutes().stream()
+                .filter(it -> !mRouteIdToAvailableDeviceRoutes.containsKey(it.getId()))
+                .map(MediaRoute2InfoHolder::createForInactiveBluetoothRoute)
+                .forEach(
+                        it -> mRouteIdToAvailableDeviceRoutes.put(it.mMediaRoute2Info.getId(), it));
+        mOnDeviceRouteChangedListener.onDeviceRouteChanged();
+    }
+
+    private MediaRoute2InfoHolder createPlaceholderBuiltinSpeakerRoute() {
+        int type = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+        return MediaRoute2InfoHolder.createForAudioManagerRoute(
+                createMediaRoute2Info(
+                        /* routeId= */ null, type, /* productName= */ null, /* address= */ null),
+                type);
+    }
+
+    @Nullable
+    private MediaRoute2Info createMediaRoute2InfoFromAudioDeviceInfo(
+            AudioDeviceInfo audioDeviceInfo) {
+        String address = audioDeviceInfo.getAddress();
+        // Passing a null route id means we want to get the default id for the route. Generally, we
+        // only expect to pass null for non-Bluetooth routes.
+        String routeId =
+                TextUtils.isEmpty(address)
+                        ? null
+                        : mBluetoothRouteController.getRouteIdForBluetoothAddress(address);
+        return createMediaRoute2Info(
+                routeId, audioDeviceInfo.getType(), audioDeviceInfo.getProductName(), address);
+    }
+
+    /**
+     * Creates a new {@link MediaRoute2Info} using the provided information.
+     *
+     * @param routeId A route id, or null to use an id pre-defined for the given {@code type}.
+     * @param audioDeviceInfoType The type as obtained from {@link AudioDeviceInfo#getType}.
+     * @param productName The product name as obtained from {@link
+     *     AudioDeviceInfo#getProductName()}, or null to use a predefined name for the given {@code
+     *     type}.
+     * @param address The type as obtained from {@link AudioDeviceInfo#getAddress()} or {@link
+     *     BluetoothDevice#getAddress()}.
+     * @return The new {@link MediaRoute2Info}.
+     */
+    @Nullable
+    private MediaRoute2Info createMediaRoute2Info(
+            @Nullable String routeId,
+            int audioDeviceInfoType,
+            @Nullable CharSequence productName,
+            @Nullable String address) {
+        SystemRouteInfo systemRouteInfo =
+                AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.get(audioDeviceInfoType);
+        if (systemRouteInfo == null) {
+            // Device type that's intentionally unsupported for media output, like the built-in
+            // earpiece.
+            return null;
+        }
+        CharSequence humanReadableName = productName;
+        if (TextUtils.isEmpty(humanReadableName)) {
+            humanReadableName = mContext.getResources().getText(systemRouteInfo.mNameResource);
+        }
+        if (routeId == null) {
+            // The caller hasn't provided an id, so we use a pre-defined one. This happens when we
+            // are creating a non-BT route, or we are creating a BT route but a race condition
+            // caused AudioManager to expose the BT route before BluetoothAdapter, preventing us
+            // from getting an id using BluetoothRouteController#getRouteIdForBluetoothAddress.
+            routeId = systemRouteInfo.mDefaultRouteId;
+        }
+        return 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();
+    }
+
+    /**
+     * Holds a {@link MediaRoute2Info} and associated information that we don't want to put in the
+     * {@link MediaRoute2Info} class because it's solely necessary for the implementation of this
+     * class.
+     */
+    private static class MediaRoute2InfoHolder {
+
+        public final MediaRoute2Info mMediaRoute2Info;
+        public final int mAudioDeviceInfoType;
+        public final boolean mCorrespondsToInactiveBluetoothRoute;
+
+        public static MediaRoute2InfoHolder createForAudioManagerRoute(
+                MediaRoute2Info mediaRoute2Info, int audioDeviceInfoType) {
+            return new MediaRoute2InfoHolder(
+                    mediaRoute2Info,
+                    audioDeviceInfoType,
+                    /* correspondsToInactiveBluetoothRoute= */ false);
+        }
+
+        public static MediaRoute2InfoHolder createForInactiveBluetoothRoute(
+                MediaRoute2Info mediaRoute2Info) {
+            // There's no corresponding audio device info, hence the audio device info type is
+            // unknown.
+            return new MediaRoute2InfoHolder(
+                    mediaRoute2Info,
+                    /* audioDeviceInfoType= */ AudioDeviceInfo.TYPE_UNKNOWN,
+                    /* correspondsToInactiveBluetoothRoute= */ true);
+        }
+
+        private MediaRoute2InfoHolder(
+                MediaRoute2Info mediaRoute2Info,
+                int audioDeviceInfoType,
+                boolean correspondsToInactiveBluetoothRoute) {
+            mMediaRoute2Info = mediaRoute2Info;
+            mAudioDeviceInfoType = audioDeviceInfoType;
+            mCorrespondsToInactiveBluetoothRoute = correspondsToInactiveBluetoothRoute;
+        }
+
+        public MediaRoute2InfoHolder copyWithVolumeInfoFromAudioManager(
+                AudioManager mAudioManager) {
+            MediaRoute2Info routeInfoWithVolumeInfo =
+                    new MediaRoute2Info.Builder(mMediaRoute2Info)
+                            .setVolumeHandling(
+                                    mAudioManager.isVolumeFixed()
+                                            ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+                                            : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+                            .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
+                            .setVolumeMax(
+                                    mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+                            .build();
+            return new MediaRoute2InfoHolder(
+                    routeInfoWithVolumeInfo,
+                    mAudioDeviceInfoType,
+                    mCorrespondsToInactiveBluetoothRoute);
         }
     }
 
     /**
-     * Checks if the given type is a device route.
-     *
-     * <p>Device route means a route which is either built-in or wired to the current device.
-     *
-     * @param type specifies the type of the device.
-     * @return {@code true} if the device is wired or built-in and {@code false} otherwise.
+     * Holds route information about an {@link AudioDeviceInfo#getType() audio device info type}.
      */
-    private boolean isDeviceRouteType(@MediaRoute2Info.Type int type) {
-        switch (type) {
-            case TYPE_BUILTIN_SPEAKER:
-            case TYPE_WIRED_HEADPHONES:
-            case TYPE_WIRED_HEADSET:
-            case TYPE_DOCK:
-            case TYPE_HDMI:
-            case TYPE_HDMI_ARC:
-            case TYPE_HDMI_EARC:
-            case TYPE_USB_DEVICE:
-                return true;
-            default:
-                return false;
+    private static class SystemRouteInfo {
+        /** The type to use for {@link MediaRoute2Info#getType()}. */
+        public final int mMediaRoute2InfoType;
+
+        /**
+         * Holds the route id to use if no other id is provided.
+         *
+         * <p>We only expect this id to be used for non-bluetooth routes. For bluetooth routes, in a
+         * normal scenario, the id is generated from the device information (like address, or
+         * hiSyncId), and this value is ignored. A non-normal scenario may occur when there's race
+         * condition between {@link BluetoothAdapter} and {@link AudioManager}, who are not
+         * synchronized.
+         */
+        public final String mDefaultRouteId;
+
+        /**
+         * The name to use for {@link MediaRoute2Info#getName()}.
+         *
+         * <p>Usually replaced by the UI layer with a localized string.
+         */
+        public final int mNameResource;
+
+        private SystemRouteInfo(int mediaRoute2InfoType, String defaultRouteId, int nameResource) {
+            mMediaRoute2InfoType = mediaRoute2InfoType;
+            mDefaultRouteId = defaultRouteId;
+            mNameResource = nameResource;
         }
     }
 
-    private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
-
+    private class AudioDeviceCallbackImpl extends AudioDeviceCallback {
+        @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
         @Override
-        public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) {
-            boolean isDeviceRouteChanged;
-            MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes);
-
-            synchronized (AudioPoliciesDeviceRouteController.this) {
-                mDeviceRoute = deviceRoute;
-                isDeviceRouteChanged = mSelectedRoute == null;
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            for (AudioDeviceInfo deviceInfo : addedDevices) {
+                if (AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.contains(deviceInfo.getType())) {
+                    // When a new valid media output is connected, we clear any routing policies so
+                    // that the default routing logic from the audio framework kicks in. As a result
+                    // of this, when the user connects a bluetooth device or a wired headset, the
+                    // new device becomes the active route, which is the traditional behavior.
+                    mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia);
+                    rebuildAvailableRoutes();
+                    break;
+                }
             }
+        }
 
-            if (isDeviceRouteChanged) {
-                mOnDeviceRouteChangedListener.onDeviceRouteChanged();
+        @RequiresPermission(
+                anyOf = {
+                    Manifest.permission.MODIFY_AUDIO_ROUTING,
+                    Manifest.permission.QUERY_AUDIO_STATE
+                })
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            for (AudioDeviceInfo deviceInfo : removedDevices) {
+                if (AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.contains(deviceInfo.getType())) {
+                    rebuildAvailableRoutes();
+                    break;
+                }
             }
         }
     }
 
+    static {
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
+                        /* defaultRouteId= */ "ROUTE_ID_BUILTIN_SPEAKER",
+                        /* nameResource= */ R.string.default_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_WIRED_HEADSET,
+                        /* defaultRouteId= */ "ROUTE_ID_WIRED_HEADSET",
+                        /* nameResource= */ R.string.default_audio_route_name_headphones));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_WIRED_HEADPHONES,
+                        /* defaultRouteId= */ "ROUTE_ID_WIRED_HEADPHONES",
+                        /* nameResource= */ R.string.default_audio_route_name_headphones));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_BLUETOOTH_A2DP,
+                        /* defaultRouteId= */ "ROUTE_ID_BLUETOOTH_A2DP",
+                        /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_HDMI,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_HDMI,
+                        /* defaultRouteId= */ "ROUTE_ID_HDMI",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_DOCK,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_DOCK,
+                        /* defaultRouteId= */ "ROUTE_ID_DOCK",
+                        /* nameResource= */ R.string.default_audio_route_name_dock_speakers));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_USB_DEVICE,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_USB_DEVICE,
+                        /* defaultRouteId= */ "ROUTE_ID_USB_DEVICE",
+                        /* nameResource= */ R.string.default_audio_route_name_usb));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_USB_HEADSET,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_USB_HEADSET,
+                        /* defaultRouteId= */ "ROUTE_ID_USB_HEADSET",
+                        /* nameResource= */ R.string.default_audio_route_name_usb));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_HDMI_ARC,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_HDMI_ARC,
+                        /* defaultRouteId= */ "ROUTE_ID_HDMI_ARC",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_HDMI_EARC,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_HDMI_EARC,
+                        /* defaultRouteId= */ "ROUTE_ID_HDMI_EARC",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        // TODO: b/305199571 - Add a proper type constants and human readable names for AUX_LINE,
+        // LINE_ANALOG, LINE_DIGITAL, BLE_BROADCAST, BLE_SPEAKER, BLE_HEADSET, and HEARING_AID.
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_HEARING_AID,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_HEARING_AID,
+                        /* defaultRouteId= */ "ROUTE_ID_HEARING_AID",
+                        /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_BLE_HEADSET,
+                        /* defaultRouteId= */ "ROUTE_ID_BLE_HEADSET",
+                        /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_BLE_HEADSET, // TODO: b/305199571 - Make a new type.
+                        /* defaultRouteId= */ "ROUTE_ID_BLE_SPEAKER",
+                        /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_BLE_BROADCAST,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_BLE_HEADSET,
+                        /* defaultRouteId= */ "ROUTE_ID_BLE_BROADCAST",
+                        /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_LINE_DIGITAL,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_UNKNOWN,
+                        /* defaultRouteId= */ "ROUTE_ID_LINE_DIGITAL",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_LINE_ANALOG,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_UNKNOWN,
+                        /* defaultRouteId= */ "ROUTE_ID_LINE_ANALOG",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_AUX_LINE,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_UNKNOWN,
+                        /* defaultRouteId= */ "ROUTE_ID_AUX_LINE",
+                        /* nameResource= */ R.string.default_audio_route_name_external_device));
+        AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                AudioDeviceInfo.TYPE_DOCK_ANALOG,
+                new SystemRouteInfo(
+                        MediaRoute2Info.TYPE_DOCK,
+                        /* defaultRouteId= */ "ROUTE_ID_DOCK_ANALOG",
+                        /* nameResource= */ R.string.default_audio_route_name_dock_speakers));
+    }
 }
diff --git a/services/core/java/com/android/server/media/AudioRoutingUtils.java b/services/core/java/com/android/server/media/AudioRoutingUtils.java
new file mode 100644
index 0000000..13f11eb
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioRoutingUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioProductStrategy;
+
+/** Holds utils related to routing in the audio framework. */
+/* package */ final class AudioRoutingUtils {
+
+    /* package */ static final AudioAttributes ATTRIBUTES_MEDIA =
+            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
+
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Nullable
+    /* package */ static AudioProductStrategy getMediaAudioProductStrategy() {
+        for (AudioProductStrategy strategy : AudioManager.getAudioProductStrategies()) {
+            if (strategy.supportsAudioAttributes(AudioRoutingUtils.ATTRIBUTES_MEDIA)) {
+                return strategy;
+            }
+        }
+        return null;
+    }
+
+    private AudioRoutingUtils() {
+        // no-op to prevent instantiation.
+    }
+}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index 2b01001..74fdf6e 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -44,19 +44,11 @@
     @NonNull
     static BluetoothRouteController createInstance(@NonNull Context context,
             @NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
-        Objects.requireNonNull(context);
         Objects.requireNonNull(listener);
+        BluetoothAdapter btAdapter = context.getSystemService(BluetoothManager.class).getAdapter();
 
-        BluetoothManager bluetoothManager = (BluetoothManager)
-                context.getSystemService(Context.BLUETOOTH_SERVICE);
-        BluetoothAdapter btAdapter = bluetoothManager.getAdapter();
-
-        if (btAdapter == null) {
+        if (btAdapter == null || Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
             return new NoOpBluetoothRouteController();
-        }
-
-        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
-            return new AudioPoliciesBluetoothRouteController(context, btAdapter, listener);
         } else {
             return new LegacyBluetoothRouteController(context, btAdapter, listener);
         }
@@ -74,17 +66,6 @@
      */
     void stop();
 
-
-    /**
-     * Selects the route with the given {@code deviceAddress}.
-     *
-     * @param deviceAddress The physical address of the device to select. May be null to unselect
-     *                      the currently selected device.
-     * @return Whether the selection succeeds. If the selection fails, the state of the instance
-     * remains unaltered.
-     */
-    boolean selectRoute(@Nullable String deviceAddress);
-
     /**
      * Transfers Bluetooth output to the given route.
      *
@@ -158,12 +139,6 @@
         }
 
         @Override
-        public boolean selectRoute(String deviceAddress) {
-            // no op
-            return false;
-        }
-
-        @Override
         public void transferTo(String routeId) {
             // no op
         }
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 0fdaaa7..9f175a9 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -16,17 +16,25 @@
 
 package com.android.server.media;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
 import android.content.Context;
 import android.media.AudioManager;
-import android.media.IAudioRoutesObserver;
 import android.media.IAudioService;
 import android.media.MediaRoute2Info;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Looper;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 
 import com.android.media.flags.Flags;
 
+import java.util.List;
+
 /**
  * Controls device routes.
  *
@@ -37,46 +45,67 @@
  */
 /* package */ interface DeviceRouteController {
 
-    /**
-     * Returns a new instance of {@link DeviceRouteController}.
-     */
-    /* package */ static DeviceRouteController createInstance(@NonNull Context context,
+    /** Returns a new instance of {@link DeviceRouteController}. */
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+    /* package */ static DeviceRouteController createInstance(
+            @NonNull Context context,
+            @NonNull Looper looper,
             @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
         AudioManager audioManager = context.getSystemService(AudioManager.class);
-        IAudioService audioService = IAudioService.Stub.asInterface(
-                ServiceManager.getService(Context.AUDIO_SERVICE));
+        AudioProductStrategy strategyForMedia = AudioRoutingUtils.getMediaAudioProductStrategy();
 
-        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
-            return new AudioPoliciesDeviceRouteController(context,
+        BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
+        BluetoothAdapter btAdapter =
+                bluetoothManager != null ? bluetoothManager.getAdapter() : null;
+
+        // TODO: b/305199571 - Make the audio policies implementation work without the need for a
+        // bluetooth adapter or a strategy for media. If no strategy for media is available we can
+        // disallow media router transfers, and without a bluetooth adapter we can remove support
+        // for transfers to inactive bluetooth routes.
+        if (strategyForMedia != null
+                && btAdapter != null
+                && Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+            return new AudioPoliciesDeviceRouteController(
+                    context,
                     audioManager,
-                    audioService,
+                    looper,
+                    strategyForMedia,
+                    btAdapter,
                     onDeviceRouteChangedListener);
         } else {
-            return new LegacyDeviceRouteController(context,
-                    audioManager,
-                    audioService,
-                    onDeviceRouteChangedListener);
+            IAudioService audioService =
+                    IAudioService.Stub.asInterface(
+                            ServiceManager.getService(Context.AUDIO_SERVICE));
+            return new LegacyDeviceRouteController(
+                    context, audioManager, audioService, onDeviceRouteChangedListener);
         }
     }
 
-    /**
-     * Select the route with the given built-in or wired {@link MediaRoute2Info.Type}.
-     *
-     * <p>If the type is {@code null} then unselects the route and falls back to the default device
-     * route observed from
-     * {@link com.android.server.audio.AudioService#startWatchingRoutes(IAudioRoutesObserver)}.
-     *
-     * @param type device type. May be {@code null} to unselect currently selected route.
-     * @return whether the selection succeeds. If the selection fails the state of the controller
-     * remains intact.
-     */
-    boolean selectRoute(@Nullable @MediaRoute2Info.Type Integer type);
-
     /** Returns the currently selected device (built-in or wired) route. */
     @NonNull
     MediaRoute2Info getSelectedRoute();
 
     /**
+     * Returns all available routes.
+     *
+     * <p>Note that this method returns available routes including the selected route because (a)
+     * this interface doesn't guarantee that the internal state of the controller won't change
+     * between calls to {@link #getSelectedRoute()} and this method and (b) {@link
+     * #getSelectedRoute()} may be treated as a transferable route (not a selected route) if the
+     * selected route is from {@link BluetoothRouteController}.
+     */
+    List<MediaRoute2Info> getAvailableRoutes();
+
+    /**
+     * Transfers device output to the given route.
+     *
+     * <p>If the route is {@code null} then active route will be deactivated.
+     *
+     * @param routeId to switch to or {@code null} to unset the active device.
+     */
+    void transferTo(@Nullable String routeId);
+
+    /**
      * Updates device route volume.
      *
      * @param volume specifies a volume for the device route or 0 for unknown.
@@ -85,6 +114,18 @@
     boolean updateVolume(int volume);
 
     /**
+     * Starts listening for changes in the system to keep an up to date view of available and
+     * selected devices.
+     */
+    void start(UserHandle mUser);
+
+    /**
+     * Stops keeping the internal state up to date with the system, releasing any resources acquired
+     * in {@link #start}
+     */
+    void stop();
+
+    /**
      * Interface for receiving events when device route has changed.
      */
     interface OnDeviceRouteChangedListener {
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index ba3cecf..041fceaf 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -132,12 +132,6 @@
         mContext.unregisterReceiver(mDeviceStateChangedReceiver);
     }
 
-    @Override
-    public boolean selectRoute(String deviceAddress) {
-        // No-op as the class decides if a route is selected based on Bluetooth events.
-        return false;
-    }
-
     /**
      * Transfers to a given bluetooth route.
      * The dedicated BT device with the route would be activated.
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index 65874e2..c0f2834 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -35,11 +35,13 @@
 import android.media.IAudioService;
 import android.media.MediaRoute2Info;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Slog;
 
 import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -73,7 +75,6 @@
     private int mDeviceVolume;
     private MediaRoute2Info mDeviceRoute;
 
-    @VisibleForTesting
     /* package */ LegacyDeviceRouteController(@NonNull Context context,
             @NonNull AudioManager audioManager,
             @NonNull IAudioService audioService,
@@ -100,9 +101,13 @@
     }
 
     @Override
-    public boolean selectRoute(@Nullable Integer type) {
-        // No-op as the controller does not support selection from the outside of the class.
-        return false;
+    public void start(UserHandle mUser) {
+        // Nothing to do.
+    }
+
+    @Override
+    public void stop() {
+        // Nothing to do.
     }
 
     @Override
@@ -112,6 +117,17 @@
     }
 
     @Override
+    public synchronized List<MediaRoute2Info> getAvailableRoutes() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public synchronized void transferTo(@Nullable String routeId) {
+        // Unsupported. This implementation doesn't support transferable routes (always exposes a
+        // single non-bluetooth route).
+    }
+
+    @Override
     public synchronized boolean updateVolume(int volume) {
         if (mDeviceVolume == volume) {
             return false;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index c8dba80..86d7833 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,15 +16,12 @@
 
 package com.android.server.media;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.media.AudioAttributes;
-import android.media.AudioDeviceAttributes;
 import android.media.AudioManager;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
@@ -51,7 +48,8 @@
  */
 // TODO: check thread safety. We may need to use lock to protect variables.
 class SystemMediaRoute2Provider extends MediaRoute2Provider {
-    private static final String TAG = "MR2SystemProvider";
+    // Package-visible to use this tag for all system routing logic (done across multiple classes).
+    /* package */ static final String TAG = "MR2SystemProvider";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final ComponentName COMPONENT_NAME = new ComponentName(
@@ -77,26 +75,6 @@
     private final AudioManagerBroadcastReceiver mAudioReceiver =
             new AudioManagerBroadcastReceiver();
 
-    private final AudioManager.OnDevicesForAttributesChangedListener
-            mOnDevicesForAttributesChangedListener =
-            new AudioManager.OnDevicesForAttributesChangedListener() {
-                @Override
-                public void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes,
-                        @NonNull List<AudioDeviceAttributes> devices) {
-                    if (attributes.getUsage() != AudioAttributes.USAGE_MEDIA) {
-                        return;
-                    }
-
-                    mHandler.post(() -> {
-                        updateSelectedAudioDevice(devices);
-                        notifyProviderState();
-                        if (updateSessionInfosIfNeeded()) {
-                            notifySessionInfoUpdated();
-                        }
-                    });
-                }
-            };
-
     private final Object mRequestLock = new Object();
     @GuardedBy("mRequestLock")
     private volatile SessionCreationRequest mPendingSessionCreationRequest;
@@ -106,7 +84,8 @@
         mIsSystemRouteProvider = true;
         mContext = context;
         mUser = user;
-        mHandler = new Handler(Looper.getMainLooper());
+        Looper looper = Looper.getMainLooper();
+        mHandler = new Handler(looper);
 
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
 
@@ -123,25 +102,15 @@
         mDeviceRouteController =
                 DeviceRouteController.createInstance(
                         context,
-                        () -> {
-                            mHandler.post(
-                                    () -> {
-                                        publishProviderState();
-                                        if (updateSessionInfosIfNeeded()) {
-                                            notifySessionInfoUpdated();
-                                        }
-                                    });
-                        });
-
-        mAudioManager.addOnDevicesForAttributesChangedListener(
-                AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(),
-                mOnDevicesForAttributesChangedListener);
-
-        // These methods below should be called after all fields are initialized, as they
-        // access the fields inside.
-        List<AudioDeviceAttributes> devices =
-                mAudioManager.getDevicesForAttributes(AudioAttributesUtils.ATTRIBUTES_MEDIA);
-        updateSelectedAudioDevice(devices);
+                        looper,
+                        () ->
+                                mHandler.post(
+                                        () -> {
+                                            publishProviderState();
+                                            if (updateSessionInfosIfNeeded()) {
+                                                notifySessionInfoUpdated();
+                                            }
+                                        }));
         updateProviderState();
         updateSessionInfosIfNeeded();
     }
@@ -151,20 +120,21 @@
         intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
         mContext.registerReceiverAsUser(mAudioReceiver, mUser,
                 intentFilter, null, null);
-
-        mHandler.post(() -> {
-            mBluetoothRouteController.start(mUser);
-            notifyProviderState();
-        });
-        updateVolume();
+        mHandler.post(
+                () -> {
+                    mDeviceRouteController.start(mUser);
+                    mBluetoothRouteController.start(mUser);
+                });
     }
 
     public void stop() {
         mContext.unregisterReceiver(mAudioReceiver);
-        mHandler.post(() -> {
-            mBluetoothRouteController.stop();
-            notifyProviderState();
-        });
+        mHandler.post(
+                () -> {
+                    mBluetoothRouteController.stop();
+                    mDeviceRouteController.stop();
+                    notifyProviderState();
+                });
     }
 
     @Override
@@ -225,13 +195,26 @@
     public void transferToRoute(long requestId, String sessionId, String routeId) {
         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;
         }
-
         MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
-        if (TextUtils.equals(routeId, selectedDeviceRoute.getId())) {
+        boolean isAvailableDeviceRoute =
+                mDeviceRouteController.getAvailableRoutes().stream()
+                        .anyMatch(it -> it.getId().equals(routeId));
+        boolean isSelectedDeviceRoute = TextUtils.equals(routeId, selectedDeviceRoute.getId());
+
+        if (isSelectedDeviceRoute || isAvailableDeviceRoute) {
+            // The requested route is managed by the device route controller. Note that the selected
+            // device route doesn't necessarily match mSelectedRouteId (which is the selected route
+            // of the routing session). If the selected device route is transferred to, we need to
+            // make the bluetooth routes inactive so that the device route becomes the selected
+            // route of the routing session.
+            mDeviceRouteController.transferTo(routeId);
             mBluetoothRouteController.transferTo(null);
         } else {
+            // The requested route is managed by the bluetooth route controller.
+            mDeviceRouteController.transferTo(null);
             mBluetoothRouteController.transferTo(routeId);
         }
     }
@@ -280,41 +263,38 @@
 
             MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
 
-            RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
-                    SYSTEM_SESSION_ID, packageName).setSystemSession(true);
+            RoutingSessionInfo.Builder builder =
+                    new RoutingSessionInfo.Builder(SYSTEM_SESSION_ID, packageName)
+                            .setSystemSession(true);
             builder.addSelectedRoute(selectedDeviceRoute.getId());
             for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
                 builder.addTransferableRoute(route.getId());
             }
+
+            if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+                for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
+                    if (!TextUtils.equals(selectedDeviceRoute.getId(), route.getId())) {
+                        builder.addTransferableRoute(route.getId());
+                    }
+                }
+            }
             return builder.setProviderId(mUniqueId).build();
         }
     }
 
-    private void updateSelectedAudioDevice(@NonNull List<AudioDeviceAttributes> devices) {
-        if (devices.isEmpty()) {
-            Slog.w(TAG, "The list of preferred devices was empty.");
-            return;
-        }
-
-        AudioDeviceAttributes audioDeviceAttributes = devices.get(0);
-
-        if (AudioAttributesUtils.isDeviceOutputAttributes(audioDeviceAttributes)) {
-            mDeviceRouteController.selectRoute(
-                    AudioAttributesUtils.mapToMediaRouteType(audioDeviceAttributes));
-            mBluetoothRouteController.selectRoute(null);
-        } else if (AudioAttributesUtils.isBluetoothOutputAttributes(audioDeviceAttributes)) {
-            mDeviceRouteController.selectRoute(null);
-            mBluetoothRouteController.selectRoute(audioDeviceAttributes.getAddress());
-        } else {
-            Slog.w(TAG, "Unknown audio attributes: " + audioDeviceAttributes);
-        }
-    }
-
     private void updateProviderState() {
         MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
 
         // We must have a device route in the provider info.
-        builder.addRoute(mDeviceRouteController.getSelectedRoute());
+        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+            List<MediaRoute2Info> deviceRoutes = mDeviceRouteController.getAvailableRoutes();
+            for (MediaRoute2Info route : deviceRoutes) {
+                builder.addRoute(route);
+            }
+            setProviderState(builder.build());
+        } else {
+            builder.addRoute(mDeviceRouteController.getSelectedRoute());
+        }
 
         for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
             builder.addRoute(route);
@@ -352,7 +332,12 @@
                             .setProviderId(mUniqueId)
                             .build();
             builder.addSelectedRoute(mSelectedRouteId);
-
+            for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
+                String routeId = route.getId();
+                if (!mSelectedRouteId.equals(routeId)) {
+                    builder.addTransferableRoute(routeId);
+                }
+            }
             for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) {
                 builder.addTransferableRoute(route.getId());
             }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 3b3d79e..07e0ddf 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -357,6 +357,12 @@
         final DeletePackageAction action;
         synchronized (mPm.mLock) {
             final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+            if (ps == null) {
+                if (DEBUG_REMOVE) {
+                    Slog.d(TAG, "Attempted to remove non-existent package " + packageName);
+                }
+                return false;
+            }
             final PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(ps);
             if (PackageManagerServiceUtils.isSystemApp(ps)
                     && mPm.checkPermission(CONTROL_KEYGUARD, packageName, UserHandle.USER_SYSTEM)
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 0a81b2b..5494bd9 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -539,6 +539,12 @@
     }
 
     @Nullable
+    public PackageSetting getScanRequestDisabledPackageSetting() {
+        assertScanResultExists();
+        return mScanResult.mRequest.mDisabledPkgSetting;
+    }
+
+    @Nullable
     public String getRealPackageName() {
         assertScanResultExists();
         return mScanResult.mRequest.mRealPkgName;
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index d0fdfa9..9384c13 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -856,9 +856,9 @@
         // We may not yet have disabled the updated package yet, so be sure to grab the
         // current setting if that's the case.
         final PackageSetting updatedSystemPs = isUpdatedSystemApp
-                ? installRequest.getDisabledPackageSetting() == null
+                ? installRequest.getScanRequestDisabledPackageSetting() == null
                 ? installRequest.getScanRequestOldPackageSetting()
-                : installRequest.getDisabledPackageSetting()
+                : installRequest.getScanRequestDisabledPackageSetting()
                 : null;
         if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null
                 || updatedSystemPs.getPkg().getLibraryNames() == null)) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 73c4224..e817df8 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3638,15 +3638,6 @@
                     return true;
                 }
                 break;
-            case KeyEvent.KEYCODE_SPACE:
-                // Handle keyboard layout switching. (META + SPACE)
-                if (firstDown && event.isMetaPressed()) {
-                    int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
-                    sendSwitchKeyboardLayout(event, direction);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH);
-                    return true;
-                }
-                break;
             case KeyEvent.KEYCODE_META_LEFT:
             case KeyEvent.KEYCODE_META_RIGHT:
                 if (down) {
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index e3f3638..5d90851 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -32,7 +32,6 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
-import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.view.accessibility.AccessibilityManager;
 
@@ -41,6 +40,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ConcurrentUtils;
+import com.android.server.utils.UserSettingDeviceConfigMediator;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -809,12 +809,13 @@
 
         private static Policy fromSettings(String settings, String deviceSpecificSettings,
                 DeviceConfig.Properties properties, String configSuffix, Policy defaultPolicy) {
-            final KeyValueListParser parser = new KeyValueListParser(',');
+            final UserSettingDeviceConfigMediator userSettingDeviceConfigMediator =
+                    new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(',');
             configSuffix = TextUtils.emptyIfNull(configSuffix);
 
             // Device-specific parameters.
             try {
-                parser.setString(deviceSpecificSettings == null ? "" : deviceSpecificSettings);
+                userSettingDeviceConfigMediator.setSettingsString(deviceSpecificSettings);
             } catch (IllegalArgumentException e) {
                 Slog.wtf(TAG, "Bad device specific battery saver constants: "
                         + deviceSpecificSettings);
@@ -822,68 +823,58 @@
 
             // Non-device-specific parameters.
             try {
-                parser.setString(settings == null ? "" : settings);
+                userSettingDeviceConfigMediator.setSettingsString(settings);
+                userSettingDeviceConfigMediator.setDeviceConfigProperties(properties);
             } catch (IllegalArgumentException e) {
                 Slog.wtf(TAG, "Bad battery saver constants: " + settings);
             }
 
             // The Settings value overrides everything, since that will be set by the user.
             // The DeviceConfig value takes second place, with the default as the last choice.
-            final float adjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR,
-                    properties.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR + configSuffix,
-                            defaultPolicy.adjustBrightnessFactor));
-            final boolean advertiseIsEnabled = parser.getBoolean(KEY_ADVERTISE_IS_ENABLED,
-                    properties.getBoolean(KEY_ADVERTISE_IS_ENABLED + configSuffix,
-                            defaultPolicy.advertiseIsEnabled));
-            final boolean deferFullBackup = parser.getBoolean(KEY_DEFER_FULL_BACKUP,
-                    properties.getBoolean(KEY_DEFER_FULL_BACKUP + configSuffix,
-                            defaultPolicy.deferFullBackup));
-            final boolean deferKeyValueBackup = parser.getBoolean(KEY_DEFER_KEYVALUE_BACKUP,
-                    properties.getBoolean(KEY_DEFER_KEYVALUE_BACKUP + configSuffix,
-                            defaultPolicy.deferKeyValueBackup));
-            final boolean disableAnimation = parser.getBoolean(KEY_DISABLE_ANIMATION,
-                    properties.getBoolean(KEY_DISABLE_ANIMATION + configSuffix,
-                            defaultPolicy.disableAnimation));
-            final boolean disableAod = parser.getBoolean(KEY_DISABLE_AOD,
-                    properties.getBoolean(KEY_DISABLE_AOD + configSuffix,
-                            defaultPolicy.disableAod));
-            final boolean disableLaunchBoost = parser.getBoolean(KEY_DISABLE_LAUNCH_BOOST,
-                    properties.getBoolean(KEY_DISABLE_LAUNCH_BOOST + configSuffix,
-                            defaultPolicy.disableLaunchBoost));
-            final boolean disableOptionalSensors = parser.getBoolean(KEY_DISABLE_OPTIONAL_SENSORS,
-                    properties.getBoolean(KEY_DISABLE_OPTIONAL_SENSORS + configSuffix,
-                            defaultPolicy.disableOptionalSensors));
-            final boolean disableVibrationConfig = parser.getBoolean(KEY_DISABLE_VIBRATION,
-                    properties.getBoolean(KEY_DISABLE_VIBRATION + configSuffix,
-                            defaultPolicy.disableVibration));
-            final boolean enableBrightnessAdjustment = parser.getBoolean(
-                    KEY_ENABLE_BRIGHTNESS_ADJUSTMENT,
-                    properties.getBoolean(KEY_ENABLE_BRIGHTNESS_ADJUSTMENT + configSuffix,
-                            defaultPolicy.enableAdjustBrightness));
-            final boolean enableDataSaver = parser.getBoolean(KEY_ENABLE_DATASAVER,
-                    properties.getBoolean(KEY_ENABLE_DATASAVER + configSuffix,
-                            defaultPolicy.enableDataSaver));
-            final boolean enableFirewall = parser.getBoolean(KEY_ENABLE_FIREWALL,
-                    properties.getBoolean(KEY_ENABLE_FIREWALL + configSuffix,
-                            defaultPolicy.enableFirewall));
-            final boolean enableNightMode = parser.getBoolean(KEY_ENABLE_NIGHT_MODE,
-                    properties.getBoolean(KEY_ENABLE_NIGHT_MODE + configSuffix,
-                            defaultPolicy.enableNightMode));
-            final boolean enableQuickDoze = parser.getBoolean(KEY_ENABLE_QUICK_DOZE,
-                    properties.getBoolean(KEY_ENABLE_QUICK_DOZE + configSuffix,
-                            defaultPolicy.enableQuickDoze));
-            final boolean forceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY,
-                    properties.getBoolean(KEY_FORCE_ALL_APPS_STANDBY + configSuffix,
-                            defaultPolicy.forceAllAppsStandby));
-            final boolean forceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK,
-                    properties.getBoolean(KEY_FORCE_BACKGROUND_CHECK + configSuffix,
-                            defaultPolicy.forceBackgroundCheck));
-            final int locationMode = parser.getInt(KEY_LOCATION_MODE,
-                    properties.getInt(KEY_LOCATION_MODE + configSuffix,
-                            defaultPolicy.locationMode));
-            final int soundTriggerMode = parser.getInt(KEY_SOUNDTRIGGER_MODE,
-                    properties.getInt(KEY_SOUNDTRIGGER_MODE + configSuffix,
-                            defaultPolicy.soundTriggerMode));
+            final float adjustBrightnessFactor = userSettingDeviceConfigMediator.getFloat(
+                    KEY_ADJUST_BRIGHTNESS_FACTOR + configSuffix,
+                    defaultPolicy.adjustBrightnessFactor);
+            final boolean advertiseIsEnabled = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ADVERTISE_IS_ENABLED + configSuffix,
+                    defaultPolicy.advertiseIsEnabled);
+            final boolean deferFullBackup = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DEFER_FULL_BACKUP + configSuffix, defaultPolicy.deferFullBackup);
+            final boolean deferKeyValueBackup = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DEFER_KEYVALUE_BACKUP + configSuffix,
+                    defaultPolicy.deferKeyValueBackup);
+            final boolean disableAnimation = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DISABLE_ANIMATION + configSuffix, defaultPolicy.disableAnimation);
+            final boolean disableAod = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DISABLE_AOD + configSuffix, defaultPolicy.disableAod);
+            final boolean disableLaunchBoost = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DISABLE_LAUNCH_BOOST + configSuffix,
+                    defaultPolicy.disableLaunchBoost);
+            final boolean disableOptionalSensors = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DISABLE_OPTIONAL_SENSORS + configSuffix,
+                    defaultPolicy.disableOptionalSensors);
+            final boolean disableVibrationConfig = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_DISABLE_VIBRATION + configSuffix, defaultPolicy.disableVibration);
+            final boolean enableBrightnessAdjustment = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ENABLE_BRIGHTNESS_ADJUSTMENT + configSuffix,
+                    defaultPolicy.enableAdjustBrightness);
+            final boolean enableDataSaver = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ENABLE_DATASAVER + configSuffix, defaultPolicy.enableDataSaver);
+            final boolean enableFirewall = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ENABLE_FIREWALL + configSuffix, defaultPolicy.enableFirewall);
+            final boolean enableNightMode = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ENABLE_NIGHT_MODE + configSuffix, defaultPolicy.enableNightMode);
+            final boolean enableQuickDoze = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_ENABLE_QUICK_DOZE + configSuffix, defaultPolicy.enableQuickDoze);
+            final boolean forceAllAppsStandby = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_FORCE_ALL_APPS_STANDBY + configSuffix,
+                    defaultPolicy.forceAllAppsStandby);
+            final boolean forceBackgroundCheck = userSettingDeviceConfigMediator.getBoolean(
+                    KEY_FORCE_BACKGROUND_CHECK + configSuffix,
+                    defaultPolicy.forceBackgroundCheck);
+            final int locationMode = userSettingDeviceConfigMediator.getInt(
+                    KEY_LOCATION_MODE + configSuffix, defaultPolicy.locationMode);
+            final int soundTriggerMode = userSettingDeviceConfigMediator.getInt(
+                    KEY_SOUNDTRIGGER_MODE + configSuffix, defaultPolicy.soundTriggerMode);
             return new Policy(
                     adjustBrightnessFactor,
                     advertiseIsEnabled,
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index dd39fb0..c17d6ab 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -51,6 +51,7 @@
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.Objects;
 
 /** An hint service implementation that runs in System Server process. */
@@ -544,7 +545,11 @@
                 if (mHalSessionPtr == 0) return;
                 mNativeWrapper.halCloseHintSession(mHalSessionPtr);
                 mHalSessionPtr = 0;
-                mToken.unlinkToDeath(this, 0);
+                try {
+                    mToken.unlinkToDeath(this, 0);
+                } catch (NoSuchElementException ignored) {
+                    Slogf.d(TAG, "Death link does not exist for session with UID " + mUid);
+                }
             }
             synchronized (mLock) {
                 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 0656a6a..59766ec 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -35,6 +35,7 @@
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
+import static android.hardware.SensorPrivacyManager.EXTRA_NOTIFICATION_ID;
 import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
 import static android.hardware.SensorPrivacyManager.EXTRA_TOGGLE_TYPE;
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
@@ -164,6 +165,7 @@
     private final AppOpsManagerInternal mAppOpsManagerInternal;
     private final TelephonyManager mTelephonyManager;
     private final PackageManagerInternal mPackageManagerInternal;
+    private final NotificationManager mNotificationManager;
 
     private CameraPrivacyLightController mCameraPrivacyLightController;
 
@@ -188,6 +190,7 @@
         mActivityTaskManager = context.getSystemService(ActivityTaskManager.class);
         mTelephonyManager = context.getSystemService(TelephonyManager.class);
         mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
+        mNotificationManager = mContext.getSystemService(NotificationManager.class);
         mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
     }
 
@@ -288,9 +291,18 @@
                 @Override
                 public void onReceive(Context context, Intent intent) {
                     setToggleSensorPrivacy(
-                            ((UserHandle) intent.getParcelableExtra(
-                                    Intent.EXTRA_USER, android.os.UserHandle.class)).getIdentifier(), OTHER,
-                            intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false);
+                            intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class)
+                                    .getIdentifier(),
+                            OTHER,
+                            intent.getIntExtra(EXTRA_SENSOR, UNKNOWN),
+                            false
+                    );
+
+                    int notificationId =
+                            intent.getIntExtra(EXTRA_NOTIFICATION_ID, SystemMessage.NOTE_UNKNOWN);
+                    if (notificationId != SystemMessage.NOTE_UNKNOWN) {
+                        mNotificationManager.cancel(notificationId);
+                    }
                 }
             }, new IntentFilter(ACTION_DISABLE_TOGGLE_SENSOR_PRIVACY),
                     MANAGE_SENSOR_PRIVACY, null, Context.RECEIVER_EXPORTED);
@@ -635,8 +647,6 @@
                 notificationId = SystemMessage.NOTE_UNBLOCK_CAM_TOGGLE;
             }
 
-            NotificationManager notificationManager =
-                    mContext.getSystemService(NotificationManager.class);
             NotificationChannel channel = new NotificationChannel(
                     SENSOR_PRIVACY_CHANNEL_ID,
                     getUiContext().getString(R.string.sensor_privacy_notification_channel_label),
@@ -646,7 +656,7 @@
             channel.enableVibration(false);
             channel.setBlockable(false);
 
-            notificationManager.createNotificationChannel(channel);
+            mNotificationManager.createNotificationChannel(channel);
 
             Icon icon = Icon.createWithResource(getUiContext().getResources(), iconRes);
 
@@ -669,10 +679,11 @@
                     new Intent(ACTION_DISABLE_TOGGLE_SENSOR_PRIVACY)
                             .setPackage(mContext.getPackageName())
                             .putExtra(EXTRA_SENSOR, sensor)
+                            .putExtra(EXTRA_NOTIFICATION_ID, notificationId)
                             .putExtra(Intent.EXTRA_USER, user),
                     PendingIntent.FLAG_IMMUTABLE
                             | PendingIntent.FLAG_UPDATE_CURRENT);
-            notificationManager.notify(notificationId,
+            mNotificationManager.notify(notificationId,
                     new Notification.Builder(mContext, SENSOR_PRIVACY_CHANNEL_ID)
                             .setContentTitle(contentTitle)
                             .setContentText(contentText)
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index e6273d3..0467d0c 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -38,6 +38,7 @@
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvRecordingInfo;
 import android.media.tv.TvTrackInfo;
+import android.media.tv.ad.ITvAdManager;
 import android.media.tv.interactive.AppLinkInfo;
 import android.media.tv.interactive.ITvInteractiveAppClient;
 import android.media.tv.interactive.ITvInteractiveAppManager;
@@ -345,6 +346,7 @@
             Slogf.d(TAG, "onStart");
         }
         publishBinderService(Context.TV_INTERACTIVE_APP_SERVICE, new BinderService());
+        publishBinderService(Context.TV_AD_SERVICE, new TvAdBinderService());
     }
 
     @Override
@@ -688,6 +690,12 @@
         }
         return session;
     }
+    private final class TvAdBinderService extends ITvAdManager.Stub {
+        @Override
+        public void startAdService(IBinder sessionToken, int userId) {
+        }
+
+    }
 
     private final class BinderService extends ITvInteractiveAppManager.Stub {
 
diff --git a/services/core/java/com/android/server/utils/UserSettingDeviceConfigMediator.java b/services/core/java/com/android/server/utils/UserSettingDeviceConfigMediator.java
new file mode 100644
index 0000000..e542349
--- /dev/null
+++ b/services/core/java/com/android/server/utils/UserSettingDeviceConfigMediator.java
@@ -0,0 +1,194 @@
+/*
+ * 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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.provider.DeviceConfig;
+import android.util.KeyValueListParser;
+
+/**
+ * Helper class to mediate the value to use when a constant exists in both a key=value pair Settings
+ * constant (that can be parsed by {@link KeyValueListParser})
+ * and the {@link DeviceConfig} properties.
+ */
+public abstract class UserSettingDeviceConfigMediator {
+    private static final String TAG = UserSettingDeviceConfigMediator.class.getSimpleName();
+
+    @Nullable
+    protected DeviceConfig.Properties mProperties;
+    @NonNull
+    protected final KeyValueListParser mSettingsParser;
+
+    /**
+     * @param keyValueListDelimiter The delimiter passed into the {@link KeyValueListParser}.
+     */
+    protected UserSettingDeviceConfigMediator(char keyValueListDelimiter) {
+        mSettingsParser = new KeyValueListParser(keyValueListDelimiter);
+    }
+
+    /**
+     * Sets the key=value list string to read from. Setting {@code null} will clear any previously
+     * set string.
+     */
+    public void setSettingsString(@Nullable String settings) {
+        mSettingsParser.setString(settings);
+    }
+
+    /**
+     * Sets the DeviceConfig Properties to read from. Setting {@code null} will clear any previously
+     * set properties.
+     */
+    public void setDeviceConfigProperties(@Nullable DeviceConfig.Properties properties) {
+        mProperties = properties;
+    }
+
+    /**
+     * Get the value for key as a boolean.
+     *
+     * @param key          The key to lookup.
+     * @param defaultValue The value to return if the key was not found, or not properly defined.
+     */
+    public abstract boolean getBoolean(@NonNull String key, boolean defaultValue);
+
+    /**
+     * Get the value for key as a float.
+     *
+     * @param key          The key to lookup.
+     * @param defaultValue The value to return if the key was not found, or not properly defined.
+     */
+    public abstract float getFloat(@NonNull String key, float defaultValue);
+
+    /**
+     * Get the value for key as an int.
+     *
+     * @param key          The key to lookup.
+     * @param defaultValue The value to return if the key was not found, or not properly defined.
+     */
+    public abstract int getInt(@NonNull String key, int defaultValue);
+
+    /**
+     * Get the value for key as a long.
+     *
+     * @param key          The key to lookup.
+     * @param defaultValue The value to return if the key was not found, or not properly defined.
+     */
+    public abstract long getLong(@NonNull String key, long defaultValue);
+
+    /**
+     * Get the value for key as a String.
+     *
+     * @param key          The key to lookup.
+     * @param defaultValue The value to return if the key was not found, or not properly defined.
+     */
+    public abstract String getString(@NonNull String key, @Nullable String defaultValue);
+
+    /**
+     * A mediator in which the existence of a single settings key-value pair will override usage
+     * of DeviceConfig properties. That is, if the Settings constant has any values set,
+     * then everything in the DeviceConfig namespace will be ignored.
+     */
+    public static class SettingsOverridesAllMediator extends UserSettingDeviceConfigMediator {
+        public SettingsOverridesAllMediator(char keyValueListDelimiter) {
+            super(keyValueListDelimiter);
+        }
+
+        @Override
+        public boolean getBoolean(@NonNull String key, boolean defaultValue) {
+            if (mSettingsParser.size() == 0) {
+                return mProperties == null
+                        ? defaultValue : mProperties.getBoolean(key, defaultValue);
+            }
+            return mSettingsParser.getBoolean(key, defaultValue);
+        }
+
+        @Override
+        public float getFloat(@NonNull String key, float defaultValue) {
+            if (mSettingsParser.size() == 0) {
+                return mProperties == null ? defaultValue : mProperties.getFloat(key, defaultValue);
+            }
+            return mSettingsParser.getFloat(key, defaultValue);
+        }
+
+        @Override
+        public int getInt(@NonNull String key, int defaultValue) {
+            if (mSettingsParser.size() == 0) {
+                return mProperties == null ? defaultValue : mProperties.getInt(key, defaultValue);
+            }
+            return mSettingsParser.getInt(key, defaultValue);
+        }
+
+        @Override
+        public long getLong(@NonNull String key, long defaultValue) {
+            if (mSettingsParser.size() == 0) {
+                return mProperties == null ? defaultValue : mProperties.getLong(key, defaultValue);
+            }
+            return mSettingsParser.getLong(key, defaultValue);
+        }
+
+        @Override
+        public String getString(@NonNull String key, @Nullable String defaultValue) {
+            if (mSettingsParser.size() == 0) {
+                return mProperties == null
+                        ? defaultValue : mProperties.getString(key, defaultValue);
+            }
+            return mSettingsParser.getString(key, defaultValue);
+        }
+    }
+
+    /**
+     * A mediator in which only individual keys in the DeviceConfig namespace will be overridden
+     * by the same key in the Settings constant. If the Settings constant does not have a specific
+     * key set, then the DeviceConfig value will be used instead.
+     */
+    public static class SettingsOverridesIndividualMediator
+            extends UserSettingDeviceConfigMediator {
+        public SettingsOverridesIndividualMediator(char keyValueListDelimiter) {
+            super(keyValueListDelimiter);
+        }
+
+        @Override
+        public boolean getBoolean(@NonNull String key, boolean defaultValue) {
+            return mSettingsParser.getBoolean(key,
+                    mProperties == null ? defaultValue : mProperties.getBoolean(key, defaultValue));
+        }
+
+        @Override
+        public float getFloat(@NonNull String key, float defaultValue) {
+            return mSettingsParser.getFloat(key,
+                    mProperties == null ? defaultValue : mProperties.getFloat(key, defaultValue));
+        }
+
+        @Override
+        public int getInt(@NonNull String key, int defaultValue) {
+            return mSettingsParser.getInt(key,
+                    mProperties == null ? defaultValue : mProperties.getInt(key, defaultValue));
+        }
+
+        @Override
+        public long getLong(@NonNull String key, long defaultValue) {
+            return mSettingsParser.getLong(key,
+                    mProperties == null ? defaultValue : mProperties.getLong(key, defaultValue));
+        }
+
+        @Override
+        public String getString(@NonNull String key, @Nullable String defaultValue) {
+            return mSettingsParser.getString(key,
+                    mProperties == null ? defaultValue : mProperties.getString(key, defaultValue));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index b12da61..e9c4096 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -258,6 +258,11 @@
         }
 
         @Override // Binder call
+        public WebViewProviderInfo getDefaultWebViewPackage() {
+            return WebViewUpdateService.this.mImpl.getDefaultWebViewPackage();
+        }
+
+        @Override // Binder call
         public WebViewProviderInfo[] getAllWebViewPackages() {
             return WebViewUpdateService.this.mImpl.getWebViewPackages();
         }
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 26c3fba..60dc4ff 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -421,6 +421,13 @@
         return providers;
     }
 
+    @Override
+    public WebViewProviderInfo getDefaultWebViewPackage() {
+        throw new IllegalStateException(
+                "getDefaultWebViewPackage shouldn't be called if update_service_v2 flag is"
+                        + " disabled.");
+    }
+
     private static class ProviderAndPackageInfo {
         public final WebViewProviderInfo provider;
         public final PackageInfo packageInfo;
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index 89cb4c8..29782d9 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -24,6 +24,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
 import android.util.Slog;
 import android.webkit.UserPackage;
 import android.webkit.WebViewFactory;
@@ -374,6 +375,23 @@
         return providers;
     }
 
+    /**
+     * Returns the default WebView provider which should be first availableByDefault option in the
+     * system config.
+     */
+    @Override
+    public WebViewProviderInfo getDefaultWebViewPackage() {
+        WebViewProviderInfo[] webviewProviders = getWebViewPackages();
+        for (WebViewProviderInfo provider : webviewProviders) {
+            if (provider.availableByDefault) {
+                return provider;
+            }
+        }
+        // This should be unreachable because the config parser enforces that there is at least one
+        // availableByDefault provider.
+        throw new AndroidRuntimeException("No available by default WebView Provider.");
+    }
+
     private static class ProviderAndPackageInfo {
         public final WebViewProviderInfo provider;
         public final PackageInfo packageInfo;
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
index a9c3dc4..1772ef9 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
@@ -40,6 +40,8 @@
 
     WebViewProviderInfo[] getValidWebViewPackages();
 
+    WebViewProviderInfo getDefaultWebViewPackage();
+
     PackageInfo getCurrentWebViewPackage();
 
     boolean isMultiProcessEnabled();
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d6302e0..bbaa691 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1582,6 +1582,7 @@
                 // An activity has changed order/visibility or the task is occluded by a transient
                 // activity, so this isn't just deliver-to-top
                 && mMovedToTopActivity == null
+                && !transitionController.hasOrderChanges()
                 && !transitionController.isTransientHide(startedActivityRootTask)) {
             // We just delivered to top, so there isn't an actual transition here.
             if (!forceTransientTransition) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 39e900a..4625b4fe 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -60,9 +60,9 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.Preconditions;
 import com.android.server.UiThread;
 import com.android.server.am.PendingIntentRecord;
+import com.android.window.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.util.HashMap;
@@ -236,6 +236,7 @@
         private final @ActivityManager.ProcessState int mCallingUidProcState;
         private final boolean mIsCallingUidPersistentSystemProcess;
         private final BackgroundStartPrivileges mBalAllowedByPiSender;
+        private final BackgroundStartPrivileges mBalAllowedByPiCreatorWithHardening;
         private final BackgroundStartPrivileges mBalAllowedByPiCreator;
         private final String mRealCallingPackage;
         private final int mRealCallingUid;
@@ -267,20 +268,33 @@
             mIntent = intent;
             mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
             if (originatingPendingIntent == null) {
-                // grant creator BAL privileges unless explicitly opted out
-                mBalAllowedByPiCreator =
+                // grant BAL privileges unless explicitly opted out
+                mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
                         checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
                                 == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
                                 ? BackgroundStartPrivileges.NONE
                                 : BackgroundStartPrivileges.ALLOW_BAL;
+                mBalAllowedByPiSender =
+                        checkedOptions.getPendingIntentBackgroundActivityStartMode()
+                                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+                                ? BackgroundStartPrivileges.NONE
+                                : BackgroundStartPrivileges.ALLOW_BAL;
             } else {
                 // for PendingIntents we restrict BAL based on target_sdk
-                mBalAllowedByPiCreator = getBackgroundStartPrivilegesAllowedByCreator(
+                mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator(
                         callingUid, callingPackage, checkedOptions);
+                final BackgroundStartPrivileges mBalAllowedByPiCreatorWithoutHardening =
+                        checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+                                ? BackgroundStartPrivileges.NONE
+                                : BackgroundStartPrivileges.ALLOW_BAL;
+                mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator()
+                        ? mBalAllowedByPiCreatorWithHardening
+                        : mBalAllowedByPiCreatorWithoutHardening;
+                mBalAllowedByPiSender =
+                        PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
+                                checkedOptions, realCallingUid, mRealCallingPackage);
             }
-            mBalAllowedByPiSender =
-                    PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
-                            checkedOptions, realCallingUid, mRealCallingPackage);
             mAppSwitchState = mService.getBalAppSwitchesState();
             mCallingUidProcState = mService.mActiveUids.getUidState(callingUid);
             mIsCallingUidPersistentSystemProcess =
@@ -319,10 +333,6 @@
                     return BackgroundStartPrivileges.NONE;
                 case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
                     // no explicit choice by the app - let us decide what to do
-                    if (!balRequireOptInByPendingIntentCreator()) {
-                        // if feature is disabled allow
-                        return BackgroundStartPrivileges.ALLOW_BAL;
-                    }
                     if (callingPackage != null) {
                         // determine based on the calling/creating package
                         boolean changeEnabled = CompatChanges.isChangeEnabled(
@@ -367,11 +377,6 @@
             return mOriginatingPendingIntent != null && hasRealCaller();
         }
 
-        private String dump(BalVerdict resultIfPiCreatorAllowsBal) {
-            Preconditions.checkState(!isPendingIntent());
-            return dump(resultIfPiCreatorAllowsBal, null);
-        }
-
         private boolean callerIsRealCaller() {
             return mCallingUid == mRealCallingUid;
         }
@@ -396,6 +401,8 @@
                 sb.append("; inVisibleTask: ").append(mCallerApp.hasActivityInVisibleTask());
             }
             sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator);
+            sb.append("; balAllowedByPiCreatorWithHardening: ")
+                    .append(mBalAllowedByPiCreatorWithHardening);
             sb.append("; resultIfPiCreatorAllowsBal: ").append(resultIfPiCreatorAllowsBal);
             sb.append("; hasRealCaller: ").append(hasRealCaller());
             sb.append("; isPendingIntent: ").append(isPendingIntent());
@@ -579,11 +586,12 @@
                     resultForCaller.allows() && resultForRealCaller.blocks());
         }
 
+        // Handle cases with explicit opt-in
         if (resultForCaller.allows()
                 && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
                 == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
             if (DEBUG_ACTIVITY_STARTS) {
-                Slog.d(TAG, "Activity start explicitly allowed by PI creator. "
+                Slog.d(TAG, "Activity start explicitly allowed by caller. "
                         + state.dump(resultForCaller, resultForRealCaller));
             }
             return statsLog(resultForCaller, state);
@@ -592,11 +600,12 @@
                 && checkedOptions.getPendingIntentBackgroundActivityStartMode()
                 == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
             if (DEBUG_ACTIVITY_STARTS) {
-                Slog.d(TAG, "Activity start explicitly allowed by PI sender. "
+                Slog.d(TAG, "Activity start explicitly allowed by real caller. "
                         + state.dump(resultForCaller, resultForRealCaller));
             }
             return statsLog(resultForRealCaller, state);
         }
+        // Handle PendingIntent cases with default behavior next
         boolean callerCanAllow = resultForCaller.allows()
                 && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
                 == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
@@ -716,17 +725,18 @@
         // is allowed, or apps like live wallpaper with non app visible window will be allowed.
         final boolean appSwitchAllowedOrFg =
                 appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY;
-        final boolean allowCallingUidStartActivity =
-                ((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
-                        && callingUidHasAnyVisibleWindow)
-                        || isCallingUidPersistentSystemProcess;
-        if (allowCallingUidStartActivity) {
+        if (appSwitchAllowedOrFg && callingUidHasAnyVisibleWindow) {
             return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
-                    /*background*/ false,
-                    "callingUidHasAnyVisibleWindow = "
-                            + callingUid
-                            + ", isCallingUidPersistentSystemProcess = "
-                            + isCallingUidPersistentSystemProcess);
+                    /*background*/ false, "callingUid has visible window");
+        }
+        if (mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) {
+            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+                    /*background*/ false, "callingUid has non-app visible window");
+        }
+
+        if (isCallingUidPersistentSystemProcess) {
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
+                    /*background*/ false, "callingUid is persistent system process");
         }
 
         // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
@@ -806,13 +816,29 @@
                     "realCallingUid has BAL permission.");
         }
 
-        // don't abort if the realCallingUid has a visible window
-        // TODO(b/171459802): We should check appSwitchAllowed also
-        if (state.mRealCallingUidHasAnyVisibleWindow) {
-            return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
-                    /*background*/ false,
-                    "realCallingUid has visible (non-toast) window.");
+        // Normal apps with visible app window will be allowed to start activity if app switching
+        // is allowed, or apps like live wallpaper with non app visible window will be allowed.
+        final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
+                || state.mAppSwitchState == APP_SWITCH_FG_ONLY;
+        if (Flags.balImproveRealCallerVisibilityCheck()) {
+            if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
+                return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                        /*background*/ false, "realCallingUid has visible window");
+            }
+            if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
+                return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                        /*background*/ false, "realCallingUid has non-app visible window");
+            }
+        } else {
+            // don't abort if the realCallingUid has a visible window
+            // TODO(b/171459802): We should check appSwitchAllowed also
+            if (state.mRealCallingUidHasAnyVisibleWindow) {
+                return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
+                        /*background*/ false,
+                        "realCallingUid has visible (non-toast) window.");
+            }
         }
+
         // if the realCallingUid is a persistent system process, abort if the IntentSender
         // wasn't allowed to start an activity
         if (state.mForcedBalByPiSender.allowsBackgroundActivityStarts()
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 2fabb0e..7ce9de4 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -83,6 +83,7 @@
     /**
      * Mark all dims as pending completion on the next call to {@link #updateDims}
      *
+     * Called before iterating on mHost's children, first step of dimming.
      * This is intended for us by the host container, to be called at the beginning of
      * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
      * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
@@ -100,8 +101,7 @@
 
     /**
      * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
-     * described in {@link #resetDimStates}. The dim bounds returned by {@link #resetDimStates}
-     * should be set before calling this method.
+     * described in {@link #resetDimStates}.
      *
      * @param t      A transaction in which to update the dims.
      * @return true if any Dims were updated.
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
new file mode 100644
index 0000000..e91857f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
+
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+
+/**
+ * Contains the information relative to the changes to apply to the dim layer
+ */
+public class DimmerAnimationHelper {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "DimmerAnimationHelper" : TAG_WM;
+    private static final int DEFAULT_DIM_ANIM_DURATION_MS = 200;
+
+    /**
+     * Contains the requested changes
+     */
+    static class Change {
+        private float mAlpha = -1f;
+        private int mBlurRadius = -1;
+        private WindowContainer mDimmingContainer = null;
+        private int mRelativeLayer = -1;
+        private static final float EPSILON = 0.0001f;
+
+        Change() {}
+
+        Change(Change other) {
+            mAlpha = other.mAlpha;
+            mBlurRadius = other.mBlurRadius;
+            mDimmingContainer = other.mDimmingContainer;
+            mRelativeLayer = other.mRelativeLayer;
+        }
+
+        // Same alpha and blur
+        boolean hasSameVisualProperties(Change other) {
+            return Math.abs(mAlpha - other.mAlpha) < EPSILON && mBlurRadius == other.mBlurRadius;
+        }
+
+        boolean hasSameDimmingContainer(Change other) {
+            return mDimmingContainer != null && mDimmingContainer == other.mDimmingContainer;
+        }
+
+        void inheritPropertiesFromAnimation(AnimationSpec anim) {
+            mAlpha = anim.mCurrentAlpha;
+            mBlurRadius = anim.mCurrentBlur;
+        }
+
+        @Override
+        public String toString() {
+            return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container="
+                    + mDimmingContainer + ", relativePosition=" + mRelativeLayer;
+        }
+    }
+
+    private Change mCurrentProperties = new Change();
+    private Change mRequestedProperties = new Change();
+    private AnimationSpec mAlphaAnimationSpec;
+
+    private final AnimationAdapterFactory mAnimationAdapterFactory;
+    private AnimationAdapter mLocalAnimationAdapter;
+
+    DimmerAnimationHelper(AnimationAdapterFactory animationFactory) {
+        mAnimationAdapterFactory = animationFactory;
+    }
+
+    void setExitParameters() {
+        setRequestedRelativeParent(mRequestedProperties.mDimmingContainer, -1 /* relativeLayer */);
+        setRequestedAppearance(0f /* alpha */, 0 /* blur */);
+    }
+
+    // Sets a requested change without applying it immediately
+    void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) {
+        mRequestedProperties.mDimmingContainer = relativeParent;
+        mRequestedProperties.mRelativeLayer = relativeLayer;
+    }
+
+    // Sets a requested change without applying it immediately
+    void setRequestedAppearance(float alpha, int blurRadius) {
+        mRequestedProperties.mAlpha = alpha;
+        mRequestedProperties.mBlurRadius = blurRadius;
+    }
+
+    /**
+     * Commit the last changes we received. Called after
+     * {@link Change#setExitParameters()},
+     * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or
+     * {@link Change#setRequestedAppearance(float, int)}
+     */
+    void applyChanges(SurfaceControl.Transaction t, SmoothDimmer.DimState dim) {
+        if (mRequestedProperties.mDimmingContainer == null) {
+            Log.e(TAG, this + " does not have a dimming container. Have you forgotten to "
+                    + "call adjustRelativeLayer?");
+            return;
+        }
+        if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
+            Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
+                    + "does not have a surface");
+            dim.remove(t);
+            return;
+        }
+
+        dim.ensureVisible(t);
+        relativeReparent(dim.mDimSurface,
+                mRequestedProperties.mDimmingContainer.getSurfaceControl(),
+                mRequestedProperties.mRelativeLayer, t);
+
+        if (!mCurrentProperties.hasSameVisualProperties(mRequestedProperties)) {
+            stopCurrentAnimation(dim.mDimSurface);
+
+            if (dim.mSkipAnimation
+                    // If the container doesn't change but requests a dim change, then it is
+                    // directly providing us the animated values
+                    || (mRequestedProperties.hasSameDimmingContainer(mCurrentProperties)
+                    && dim.isDimming())) {
+                ProtoLog.d(WM_DEBUG_DIMMER,
+                        "%s skipping animation and directly setting alpha=%f, blur=%d",
+                        dim, mRequestedProperties.mAlpha,
+                        mRequestedProperties.mBlurRadius);
+                setAlphaBlur(dim.mDimSurface, mRequestedProperties.mAlpha,
+                        mRequestedProperties.mBlurRadius, t);
+                dim.mSkipAnimation = false;
+            } else {
+                startAnimation(t, dim);
+            }
+
+        } else if (!dim.isDimming()) {
+            // We are not dimming, so we tried the exit animation but the alpha is already 0,
+            // therefore, let's just remove this surface
+            dim.remove(t);
+        }
+        mCurrentProperties = new Change(mRequestedProperties);
+    }
+
+    private void startAnimation(
+            SurfaceControl.Transaction t, SmoothDimmer.DimState dim) {
+        ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on %s", dim);
+        mAlphaAnimationSpec = getRequestedAnimationSpec();
+        mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
+                dim.mHostContainer.mWmService.mSurfaceAnimationRunner);
+
+        float targetAlpha = mRequestedProperties.mAlpha;
+        int targetBlur = mRequestedProperties.mBlurRadius;
+
+        mLocalAnimationAdapter.startAnimation(dim.mDimSurface, t,
+                ANIMATION_TYPE_DIMMER, /* finishCallback */ (type, animator) -> {
+                    setAlphaBlur(dim.mDimSurface, targetAlpha, targetBlur, t);
+                    if (targetAlpha == 0f && !dim.isDimming()) {
+                        dim.remove(t);
+                    }
+                    mLocalAnimationAdapter = null;
+                    mAlphaAnimationSpec = null;
+                });
+    }
+
+    private boolean isAnimating() {
+        return mAlphaAnimationSpec != null;
+    }
+
+    void stopCurrentAnimation(SurfaceControl surface) {
+        if (mLocalAnimationAdapter != null && isAnimating()) {
+            // Save the current animation progress and cancel the animation
+            mCurrentProperties.inheritPropertiesFromAnimation(mAlphaAnimationSpec);
+            mLocalAnimationAdapter.onAnimationCancelled(surface);
+            mLocalAnimationAdapter = null;
+            mAlphaAnimationSpec = null;
+        }
+    }
+
+    private AnimationSpec getRequestedAnimationSpec() {
+        final float startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
+        final int startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
+        long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer)
+                * Math.abs(mRequestedProperties.mAlpha - startAlpha));
+
+        final AnimationSpec spec =  new AnimationSpec(
+                new AnimationSpec.AnimationExtremes<>(startAlpha, mRequestedProperties.mAlpha),
+                new AnimationSpec.AnimationExtremes<>(startBlur, mRequestedProperties.mBlurRadius),
+                duration
+        );
+        ProtoLog.v(WM_DEBUG_DIMMER, "Dim animation requested: %s", spec);
+        return spec;
+    }
+
+    /**
+     * Change the relative parent of this dim layer
+     */
+    void relativeReparent(SurfaceControl dimLayer, SurfaceControl relativeParent,
+                          int relativePosition, SurfaceControl.Transaction t) {
+        try {
+            t.setRelativeLayer(dimLayer, relativeParent, relativePosition);
+        } catch (NullPointerException e) {
+            Log.w(TAG, "Tried to change parent of dim " + dimLayer + " after remove", e);
+        }
+    }
+
+    void setAlphaBlur(SurfaceControl sc, float alpha, int blur, SurfaceControl.Transaction t) {
+        try {
+            t.setAlpha(sc, alpha);
+            t.setBackgroundBlurRadius(sc, blur);
+        } catch (NullPointerException e) {
+            Log.w(TAG , "Tried to change look of dim " + sc + " after remove",  e);
+        }
+    }
+
+    private long getDimDuration(WindowContainer container) {
+        // Use the same duration as the animation on the WindowContainer
+        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION_MS * durationScale)
+                : animationAdapter.getDurationHint();
+    }
+
+    /**
+     * Collects the animation specifics
+     */
+    static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+        private static final String TAG = TAG_WITH_CLASS_NAME ? "DimmerAnimationSpec" : TAG_WM;
+
+        static class AnimationExtremes<T> {
+            final T mStartValue;
+            final T mFinishValue;
+
+            AnimationExtremes(T fromValue, T toValue) {
+                mStartValue = fromValue;
+                mFinishValue = toValue;
+            }
+
+            @Override
+            public String toString() {
+                return "[" + mStartValue + "->" + mFinishValue + "]";
+            }
+        }
+
+        private final long mDuration;
+        private final AnimationSpec.AnimationExtremes<Float> mAlpha;
+        private final AnimationSpec.AnimationExtremes<Integer> mBlur;
+
+        float mCurrentAlpha = 0;
+        int mCurrentBlur = 0;
+        boolean mStarted = false;
+
+        AnimationSpec(AnimationSpec.AnimationExtremes<Float> alpha,
+                      AnimationSpec.AnimationExtremes<Integer> blur, long duration) {
+            mAlpha = alpha;
+            mBlur = blur;
+            mDuration = duration;
+        }
+
+        @Override
+        public long getDuration() {
+            return mDuration;
+        }
+
+        @Override
+        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+            if (!mStarted) {
+                // The first frame would end up in the sync transaction, and since this could be
+                // applied after the animation transaction, we avoid putting visible changes here.
+                // The initial state of the animation matches the current state of the dim anyway.
+                mStarted = true;
+                return;
+            }
+            final float fraction = getFraction(currentPlayTime);
+            mCurrentAlpha =
+                    fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue;
+            mCurrentBlur =
+                    (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue;
+            if (sc.isValid()) {
+                t.setAlpha(sc, mCurrentAlpha);
+                t.setBackgroundBlurRadius(sc, mCurrentBlur);
+            } else {
+                Log.w(TAG, "Dimmer#AnimationSpec tried to access " + sc + " after release");
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Animation spec: alpha=" + mAlpha + ", blur=" + mBlur;
+        }
+
+        @Override
+        public void dump(PrintWriter pw, String prefix) {
+            pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue);
+            pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue);
+            pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue);
+            pw.print(" to_blur="); pw.print(mBlur.mFinishValue);
+            pw.print(" duration="); pw.println(mDuration);
+        }
+
+        @Override
+        public void dumpDebugInner(ProtoOutputStream proto) {
+            final long token = proto.start(ALPHA);
+            proto.write(FROM, mAlpha.mStartValue);
+            proto.write(TO, mAlpha.mFinishValue);
+            proto.write(DURATION_MS, mDuration);
+            proto.end(token);
+        }
+    }
+
+    static class AnimationAdapterFactory {
+        public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+                                    SurfaceAnimationRunner runner) {
+            return new LocalAnimationAdapter(alphaAnimationSpec, runner);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index bb59936..50376fe 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1648,7 +1648,7 @@
                 ? new Transition.ReadyCondition("displayConfig", this) : null;
         if (displayConfig != null) {
             mTransitionController.waitFor(displayConfig);
-        } else if (mTransitionController.isShellTransitionsEnabled()) {
+        } else if (mTransitionController.isShellTransitionsEnabled() && mLastHasContent) {
             Slog.e(TAG, "Display reconfigured outside of a transition: " + this);
         }
         final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java
index 2549bbf..b5d94a2 100644
--- a/services/core/java/com/android/server/wm/SmoothDimmer.java
+++ b/services/core/java/com/android/server/wm/SmoothDimmer.java
@@ -17,265 +17,203 @@
 package com.android.server.wm;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
-import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
-import static com.android.server.wm.AlphaAnimationSpecProto.TO;
-import static com.android.server.wm.AnimationSpecProto.ALPHA;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.graphics.Rect;
 import android.util.Log;
-import android.util.proto.ProtoOutputStream;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 
-import java.io.PrintWriter;
-
 class SmoothDimmer extends Dimmer {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
-    private static final float EPSILON = 0.0001f;
-    // This is in milliseconds.
-    private static final int DEFAULT_DIM_ANIM_DURATION = 200;
-    DimState mDimState;
-    private WindowContainer mLastRequestedDimContainer;
-    private final AnimationAdapterFactory mAnimationAdapterFactory;
 
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+    DimState mDimState;
+    final DimmerAnimationHelper.AnimationAdapterFactory mAnimationAdapterFactory;
+
+    /**
+     * Controls the dim behaviour
+     */
     @VisibleForTesting
     class DimState {
-        /**
-         * The layer where property changes should be invoked on.
-         */
-        SurfaceControl mDimLayer;
-        boolean mDimming;
-        boolean mIsVisible;
-
+        /** Related objects */
+        SurfaceControl mDimSurface;
+        final WindowContainer mHostContainer;
+        // The last container to request to dim
+        private WindowContainer mLastRequestedDimContainer;
+        /** Animation */
+        private final DimmerAnimationHelper mAnimationHelper;
+        boolean mSkipAnimation = false;
+        // Determines whether the dim layer should animate before destroying.
+        boolean mAnimateExit = true;
+        /** Surface visibility and bounds */
+        private boolean mIsVisible = false;
         // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
         final Rect mDimBounds = new Rect();
 
-        /**
-         * Determines whether the dim layer should animate before destroying.
-         */
-        boolean mAnimateExit = true;
-
-        /**
-         * Used for Dims not associated with a WindowContainer.
-         * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim
-         * lifecycle.
-         */
-        boolean mDontReset;
-
-        Change mCurrentProperties;
-        Change mRequestedProperties;
-        private AnimationSpec mAlphaAnimationSpec;
-        private AnimationAdapter mLocalAnimationAdapter;
-
-        static class Change {
-            private float mAlpha = -1f;
-            private int mBlurRadius = -1;
-            private WindowContainer mDimmingContainer = null;
-            private int mRelativeLayer = -1;
-            private boolean mSkipAnimation = false;
-
-            Change() {}
-
-            Change(Change other) {
-                mAlpha = other.mAlpha;
-                mBlurRadius = other.mBlurRadius;
-                mDimmingContainer = other.mDimmingContainer;
-                mRelativeLayer = other.mRelativeLayer;
-            }
-
-            @Override
-            public String toString() {
-                return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container="
-                        + mDimmingContainer + ", relativePosition=" + mRelativeLayer
-                        + ", skipAnimation=" + mSkipAnimation;
-            }
-        }
-
-        DimState(SurfaceControl dimLayer) {
-            mDimLayer = dimLayer;
-            mDimming = true;
-            mCurrentProperties = new Change();
-            mRequestedProperties = new Change();
-        }
-
-        void setExitParameters(WindowContainer container) {
-            setRequestedRelativeParent(container, -1 /* relativeLayer */);
-            setRequestedAppearance(0f /* alpha */, 0 /* blur */);
-        }
-
-        // Sets a requested change without applying it immediately
-        void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) {
-            mRequestedProperties.mDimmingContainer = relativeParent;
-            mRequestedProperties.mRelativeLayer = relativeLayer;
-        }
-
-        // Sets a requested change without applying it immediately
-        void setRequestedAppearance(float alpha, int blurRadius) {
-            mRequestedProperties.mAlpha = alpha;
-            mRequestedProperties.mBlurRadius = blurRadius;
-        }
-
-        /**
-         * Commit the last changes we received. Called after
-         * {@link Change#setExitParameters(WindowContainer)},
-         * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or
-         * {@link Change#setRequestedAppearance(float, int)}
-         */
-        void applyChanges(SurfaceControl.Transaction t) {
-            if (mRequestedProperties.mDimmingContainer == null) {
-                Log.e(TAG, this + " does not have a dimming container. Have you forgotten to "
-                        + "call adjustRelativeLayer?");
-                return;
-            }
-            if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
-                Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
-                        + "does not have a surface");
-                return;
-            }
-            if (!mDimState.mIsVisible) {
-                mDimState.mIsVisible = true;
-                t.show(mDimState.mDimLayer);
-            }
-            t.setRelativeLayer(mDimLayer,
-                    mRequestedProperties.mDimmingContainer.getSurfaceControl(),
-                    mRequestedProperties.mRelativeLayer);
-
-            if (aspectChanged()) {
-                if (isAnimating()) {
-                    mLocalAnimationAdapter.onAnimationCancelled(mDimLayer);
-                }
-                if (mRequestedProperties.mSkipAnimation
-                        || (!dimmingContainerChanged() && mDimming)) {
-                    // If the dimming container has not changed, then it is running its own
-                    // animation, thus we can directly set the values we get requested, unless it's
-                    // the exiting animation
-                    ProtoLog.d(WM_DEBUG_DIMMER,
-                            "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
-                            mDimLayer, mRequestedProperties.mAlpha,
-                            mRequestedProperties.mBlurRadius);
-                    t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
-                    t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
-                    mRequestedProperties.mSkipAnimation = false;
-                } else {
-                    startAnimation(t);
-                }
-            }
-            mCurrentProperties = new Change(mRequestedProperties);
-        }
-
-        private void startAnimation(SurfaceControl.Transaction t) {
-            mAlphaAnimationSpec = getRequestedAnimationSpec(mRequestedProperties.mAlpha,
-                    mRequestedProperties.mBlurRadius);
-            mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
-                    mHost.mWmService.mSurfaceAnimationRunner);
-
-            mLocalAnimationAdapter.startAnimation(mDimLayer, t,
-                    ANIMATION_TYPE_DIMMER, (type, animator) -> {
-                        t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
-                        t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
-                        if (mRequestedProperties.mAlpha == 0f && !mDimming) {
-                            ProtoLog.d(WM_DEBUG_DIMMER,
-                                    "Removing dim surface %s on transaction %s", mDimLayer, t);
-                            t.remove(mDimLayer);
-                        }
-                        mLocalAnimationAdapter = null;
-                        mAlphaAnimationSpec = null;
-                    });
-        }
-
-        private boolean isAnimating() {
-            return mAlphaAnimationSpec != null;
-        }
-
-        private boolean aspectChanged() {
-            return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON
-                    || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius;
-        }
-
-        private boolean dimmingContainerChanged() {
-            return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer;
-        }
-
-        private AnimationSpec getRequestedAnimationSpec(float targetAlpha, int targetBlur) {
-            final float startAlpha;
-            final int startBlur;
-            if (mAlphaAnimationSpec != null) {
-                startAlpha = mAlphaAnimationSpec.mCurrentAlpha;
-                startBlur = mAlphaAnimationSpec.mCurrentBlur;
-            } else {
-                startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
-                startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
-            }
-            long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer)
-                    * Math.abs(targetAlpha - startAlpha));
-
-            ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on dim layer %s, requested by %s, "
-                            + "alpha: %f -> %f, blur: %d -> %d",
-                    mDimLayer, mRequestedProperties.mDimmingContainer, startAlpha, targetAlpha,
-                    startBlur, targetBlur);
-            return new AnimationSpec(
-                    new AnimationExtremes<>(startAlpha, targetAlpha),
-                    new AnimationExtremes<>(startBlur, targetBlur),
-                    duration
-            );
-        }
-    }
-
-    protected SmoothDimmer(WindowContainer host) {
-        this(host, new AnimationAdapterFactory());
-    }
-
-    @VisibleForTesting
-    SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) {
-        super(host);
-        mAnimationAdapterFactory = animationFactory;
-    }
-
-    private DimState obtainDimState(WindowContainer container) {
-        if (mDimState == null) {
+        DimState() {
+            mHostContainer = mHost;
+            mAnimationHelper = new DimmerAnimationHelper(mAnimationAdapterFactory);
             try {
-                final SurfaceControl ctl = makeDimLayer();
-                mDimState = new DimState(ctl);
+                mDimSurface = makeDimLayer();
             } catch (Surface.OutOfResourcesException e) {
                 Log.w(TAG, "OutOfResourcesException creating dim surface");
             }
         }
 
-        mLastRequestedDimContainer = container;
-        return mDimState;
+        void ensureVisible(SurfaceControl.Transaction t) {
+            if (!mIsVisible) {
+                t.show(mDimSurface);
+                t.setAlpha(mDimSurface, 0f);
+                mIsVisible = true;
+            }
+        }
+
+        void adjustSurfaceLayout(SurfaceControl.Transaction t) {
+            // TODO: Once we use geometry from hierarchy this falls away.
+            t.setPosition(mDimSurface, mDimBounds.left, mDimBounds.top);
+            t.setWindowCrop(mDimSurface, mDimBounds.width(), mDimBounds.height());
+        }
+
+        /**
+         * Set the parameters to prepare the dim to change its appearance
+         */
+        void prepareLookChange(float alpha, int blurRadius) {
+            mAnimationHelper.setRequestedAppearance(alpha, blurRadius);
+        }
+
+        /**
+         * Prepare the dim for the exit animation
+         */
+        void exit(SurfaceControl.Transaction t) {
+            if (!mAnimateExit) {
+                remove(t);
+            } else {
+                mAnimationHelper.setExitParameters();
+                setReady(t);
+            }
+        }
+
+        void remove(SurfaceControl.Transaction t) {
+            mAnimationHelper.stopCurrentAnimation(mDimSurface);
+            if (mDimSurface.isValid()) {
+                t.remove(mDimSurface);
+                ProtoLog.d(WM_DEBUG_DIMMER,
+                        "Removing dim surface %s on transaction %s", this, t);
+            } else {
+                Log.w(TAG, "Tried to remove " + mDimSurface + " multiple times\n");
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "SmoothDimmer#DimState with host=" + mHostContainer + ", surface=" + mDimSurface;
+        }
+
+        /**
+         * Set the parameters to prepare the dim to be relative parented to the dimming container
+         */
+        void prepareReparent(WindowContainer relativeParent, int relativeLayer) {
+            mAnimationHelper.setRequestedRelativeParent(relativeParent, relativeLayer);
+        }
+
+        /**
+         * Call when all the changes have been requested to have them applied
+         * @param t The transaction in which to apply the changes
+         */
+        void setReady(SurfaceControl.Transaction t) {
+            mAnimationHelper.applyChanges(t, this);
+        }
+
+        /**
+         * Whether anyone is currently requesting the dim
+         */
+        boolean isDimming() {
+            return mLastRequestedDimContainer != null;
+        }
+
+        private SurfaceControl makeDimLayer() {
+            return mHost.makeChildSurface(null)
+                    .setParent(mHost.getSurfaceControl())
+                    .setColorLayer()
+                    .setName("Dim Layer for - " + mHost.getName())
+                    .setCallsite("DimLayer.makeDimLayer")
+                    .build();
+        }
     }
 
-    private SurfaceControl makeDimLayer() {
-        return mHost.makeChildSurface(null)
-                .setParent(mHost.getSurfaceControl())
-                .setColorLayer()
-                .setName("Dim Layer for - " + mHost.getName())
-                .setCallsite("Dimmer.makeDimLayer")
-                .build();
+    protected SmoothDimmer(WindowContainer host) {
+        this(host, new DimmerAnimationHelper.AnimationAdapterFactory());
     }
 
-    @Override
-    SurfaceControl getDimLayer() {
-        return mDimState != null ? mDimState.mDimLayer : null;
+    @VisibleForTesting
+    SmoothDimmer(WindowContainer host,
+                 DimmerAnimationHelper.AnimationAdapterFactory animationFactory) {
+        super(host);
+        mAnimationAdapterFactory = animationFactory;
     }
 
     @Override
     void resetDimStates() {
+        if (mDimState != null) {
+            mDimState.mLastRequestedDimContainer = null;
+        }
+    }
+
+    @Override
+    protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
+        final DimState d = obtainDimState(container);
+        d.prepareLookChange(alpha, blurRadius);
+    }
+
+    @Override
+    protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
+        if (mDimState != null) {
+            mDimState.prepareReparent(container, relativeLayer);
+        }
+    }
+
+    @Override
+    boolean updateDims(SurfaceControl.Transaction t) {
         if (mDimState == null) {
-            return;
+            return false;
         }
-        if (!mDimState.mDontReset) {
-            mDimState.mDimming = false;
+        if (!mDimState.isDimming()) {
+            // No one is dimming, fade out and remove the dim
+            mDimState.exit(t);
+            mDimState = null;
+            return false;
+        } else {
+            // Someone is dimming, show the requested changes
+            mDimState.adjustSurfaceLayout(t);
+            final WindowState ws = mDimState.mLastRequestedDimContainer.asWindowState();
+            if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
+                    && ws.mActivityRecord.mStartingData != null) {
+                // Skip enter animation while starting window is on top of its activity
+                mDimState.mSkipAnimation = true;
+            }
+            mDimState.setReady(t);
+            return true;
         }
     }
 
+    private DimState obtainDimState(WindowContainer container) {
+        if (mDimState == null) {
+            mDimState = new DimState();
+        }
+        mDimState.mLastRequestedDimContainer = container;
+        return mDimState;
+    }
+
+    @Override
+    @VisibleForTesting
+    SurfaceControl getDimLayer() {
+        return mDimState != null ? mDimState.mDimSurface : null;
+    }
+
     @Override
     Rect getDimBounds() {
         return mDimState != null ? mDimState.mDimBounds : null;
@@ -287,127 +225,4 @@
             mDimState.mAnimateExit = false;
         }
     }
-
-    @Override
-    protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
-        final DimState d = obtainDimState(container);
-        mDimState.setRequestedAppearance(alpha, blurRadius);
-        d.mDimming = true;
-    }
-
-    @Override
-    protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
-        if (mDimState != null) {
-            mDimState.setRequestedRelativeParent(container, relativeLayer);
-        }
-    }
-
-    boolean updateDims(SurfaceControl.Transaction t) {
-        if (mDimState == null) {
-            return false;
-        }
-
-        if (!mDimState.mDimming) {
-            // No one is dimming anymore, fade out dim and remove
-            if (!mDimState.mAnimateExit) {
-                if (mDimState.mDimLayer.isValid()) {
-                    t.remove(mDimState.mDimLayer);
-                }
-            } else {
-                mDimState.setExitParameters(
-                        mDimState.mRequestedProperties.mDimmingContainer);
-                mDimState.applyChanges(t);
-            }
-            mDimState = null;
-            return false;
-        }
-        final Rect bounds = mDimState.mDimBounds;
-        // TODO: Once we use geometry from hierarchy this falls away.
-        t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
-        t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
-        // Skip enter animation while starting window is on top of its activity
-        final WindowState ws = mLastRequestedDimContainer.asWindowState();
-        if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
-                && ws.mActivityRecord.mStartingData != null) {
-            mDimState.mRequestedProperties.mSkipAnimation = true;
-        }
-        mDimState.applyChanges(t);
-        return true;
-    }
-
-    private long getDimDuration(WindowContainer container) {
-        // Use the same duration as the animation on the WindowContainer
-        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
-        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
-        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
-                : animationAdapter.getDurationHint();
-    }
-
-    private static class AnimationExtremes<T> {
-        final T mStartValue;
-        final T mFinishValue;
-
-        AnimationExtremes(T fromValue, T toValue) {
-            mStartValue = fromValue;
-            mFinishValue = toValue;
-        }
-    }
-
-    private static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec {
-        private final long mDuration;
-        private final AnimationExtremes<Float> mAlpha;
-        private final AnimationExtremes<Integer> mBlur;
-
-        float mCurrentAlpha = 0;
-        int mCurrentBlur = 0;
-
-        AnimationSpec(AnimationExtremes<Float> alpha,
-                AnimationExtremes<Integer> blur, long duration) {
-            mAlpha = alpha;
-            mBlur = blur;
-            mDuration = duration;
-        }
-
-        @Override
-        public long getDuration() {
-            return mDuration;
-        }
-
-        @Override
-        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
-            final float fraction = getFraction(currentPlayTime);
-            mCurrentAlpha =
-                    fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue;
-            mCurrentBlur =
-                    (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue;
-            t.setAlpha(sc, mCurrentAlpha);
-            t.setBackgroundBlurRadius(sc, mCurrentBlur);
-        }
-
-        @Override
-        public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue);
-            pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue);
-            pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue);
-            pw.print(" to_blur="); pw.print(mBlur.mFinishValue);
-            pw.print(" duration="); pw.println(mDuration);
-        }
-
-        @Override
-        public void dumpDebugInner(ProtoOutputStream proto) {
-            final long token = proto.start(ALPHA);
-            proto.write(FROM, mAlpha.mStartValue);
-            proto.write(TO, mAlpha.mFinishValue);
-            proto.write(DURATION_MS, mDuration);
-            proto.end(token);
-        }
-    }
-
-    static class AnimationAdapterFactory {
-
-        public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
-                SurfaceAnimationRunner runner) {
-            return new LocalAnimationAdapter(alphaAnimationSpec, runner);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7375512..5f08212 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -422,6 +422,11 @@
     // TODO: remove this once the recents animation is moved to the Shell
     SurfaceControl mLastRecentsAnimationOverlay;
 
+    // A surface that is used by TaskFragmentOrganizer to place content on top of own activities and
+    // trusted TaskFragments.
+    @Nullable
+    DecorSurfaceContainer mDecorSurfaceContainer;
+
     static final int LAYER_RANK_INVISIBLE = -1;
     // Ranking (from top) of this task among all visible tasks. (-1 means it's not visible)
     // This number will be assigned when we evaluate OOM scores for all visible tasks.
@@ -1540,6 +1545,11 @@
             mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
         }
 
+        if (mDecorSurfaceContainer != null && r == mDecorSurfaceContainer.mOwnerTaskFragment) {
+            // Remove the decor surface if the owner TaskFragment is removed;
+            removeDecorSurface();
+        }
+
         if (hasChild()) {
             updateEffectiveIntent();
 
@@ -2638,6 +2648,9 @@
         }
         // If applicable let the TaskOrganizer know the Task is vanishing.
         setTaskOrganizer(null);
+        if (mDecorSurfaceContainer != null) {
+            mDecorSurfaceContainer.release();
+        }
 
         super.removeImmediately();
         mRemoving = false;
@@ -3644,7 +3657,8 @@
      */
     TaskFragmentParentInfo getTaskFragmentParentInfo() {
         return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(),
-                shouldBeVisible(null /* starting */), hasNonFinishingDirectActivity());
+                shouldBeVisible(null /* starting */), hasNonFinishingDirectActivity(),
+                getDecorSurface());
     }
 
     @Override
@@ -3666,6 +3680,62 @@
         }
     }
 
+    @Override
+    void assignChildLayers(@NonNull SurfaceControl.Transaction t) {
+        int layer = 0;
+        boolean decorSurfacePlaced = false;
+
+        // We use two passes as a way to promote children which
+        // need Z-boosting to the end of the list.
+        for (int j = 0; j < mChildren.size(); ++j) {
+            final WindowContainer wc = mChildren.get(j);
+            wc.assignChildLayers(t);
+            if (!wc.needsZBoost()) {
+                // Place the decor surface under any untrusted content.
+                if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+                        && shouldPlaceDecorSurfaceBelowContainer(wc)) {
+                    mDecorSurfaceContainer.assignLayer(t, layer++);
+                    decorSurfacePlaced = true;
+                }
+                wc.assignLayer(t, layer++);
+
+                // Place the decor surface just above the owner TaskFragment.
+                if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+                        && wc == mDecorSurfaceContainer.mOwnerTaskFragment) {
+                    mDecorSurfaceContainer.assignLayer(t, layer++);
+                    decorSurfacePlaced = true;
+                }
+            }
+        }
+
+        // If not placed yet, the decor surface should be on top of all non-boosted children.
+        if (mDecorSurfaceContainer != null && !decorSurfacePlaced) {
+            mDecorSurfaceContainer.assignLayer(t, layer++);
+            decorSurfacePlaced = true;
+        }
+
+        for (int j = 0; j < mChildren.size(); ++j) {
+            final WindowContainer wc = mChildren.get(j);
+            if (wc.needsZBoost()) {
+                wc.assignLayer(t, layer++);
+            }
+        }
+        if (mOverlayHost != null) {
+            mOverlayHost.setLayer(t, layer++);
+        }
+    }
+
+    boolean shouldPlaceDecorSurfaceBelowContainer(@NonNull WindowContainer wc) {
+        boolean isOwnActivity =
+                wc.asActivityRecord() != null
+                        && wc.asActivityRecord().isUid(effectiveUid);
+        boolean isTrustedTaskFragment =
+                wc.asTaskFragment() != null
+                        && wc.asTaskFragment().isEmbedded()
+                        && wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode();
+        return !isOwnActivity && !isTrustedTaskFragment;
+    }
+
     boolean isTaskId(int taskId) {
         return mTaskId == taskId;
     }
@@ -6177,6 +6247,7 @@
                 // Avoid resuming activities on secondary displays since we don't want bubble
                 // activities to be resumed while bubble is still collapsed.
                 // TODO(b/113840485): Having keyguard going away state for secondary displays.
+                && display != null
                 && display.isDefaultDisplay) {
             return false;
         }
@@ -6672,4 +6743,77 @@
             mOverlayHost.dispatchInsetsChanged(s, mTmpRect);
         }
     }
+
+    /**
+     * Associates the decor surface with the given TF, or create one if there
+     * isn't one in the Task yet. The surface will be removed with the TF,
+     * and become invisible if the TF is invisible. */
+    void moveOrCreateDecorSurfaceFor(TaskFragment taskFragment) {
+        if (mDecorSurfaceContainer != null) {
+            mDecorSurfaceContainer.mOwnerTaskFragment = taskFragment;
+        } else {
+            mDecorSurfaceContainer = new DecorSurfaceContainer(taskFragment);
+            assignChildLayers();
+            sendTaskFragmentParentInfoChangedIfNeeded();
+        }
+    }
+
+    void removeDecorSurface() {
+        if (mDecorSurfaceContainer == null) {
+            return;
+        }
+        mDecorSurfaceContainer.release();
+        mDecorSurfaceContainer = null;
+        sendTaskFragmentParentInfoChangedIfNeeded();
+    }
+
+    @Nullable SurfaceControl getDecorSurface() {
+        return mDecorSurfaceContainer != null ? mDecorSurfaceContainer.mDecorSurface : null;
+    }
+
+    /**
+     * A decor surface that is requested by a {@code TaskFragmentOrganizer} which will be placed
+     * below children windows except for own Activities and TaskFragment in fully trusted mode.
+     */
+    @VisibleForTesting
+    class DecorSurfaceContainer {
+        @VisibleForTesting
+        @NonNull final SurfaceControl mContainerSurface;
+
+        @VisibleForTesting
+        @NonNull final SurfaceControl mDecorSurface;
+
+        // The TaskFragment that requested the decor surface. If it is destroyed, the decor surface
+        // is also released.
+        @VisibleForTesting
+        @NonNull TaskFragment mOwnerTaskFragment;
+
+        private DecorSurfaceContainer(@NonNull TaskFragment initialOwner) {
+            mOwnerTaskFragment = initialOwner;
+            mContainerSurface = makeSurface().setContainerLayer()
+                    .setParent(mSurfaceControl)
+                    .setName(mSurfaceControl + " - decor surface container")
+                    .setEffectLayer()
+                    .setHidden(false)
+                    .setCallsite("Task.DecorSurfaceContainer")
+                    .build();
+
+            mDecorSurface = makeSurface()
+                    .setParent(mContainerSurface)
+                    .setName(mSurfaceControl + " - decor surface")
+                    .setHidden(false)
+                    .setCallsite("Task.DecorSurfaceContainer")
+                    .build();
+        }
+
+        private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) {
+            t.setLayer(mContainerSurface, layer);
+            t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible());
+        }
+
+        private void release() {
+            mDecorSurface.release();
+            mContainerSurface.release();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 8bc461f..39b4480 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -316,7 +316,8 @@
     /** Organizer that organizing this TaskFragment. */
     @Nullable
     private ITaskFragmentOrganizer mTaskFragmentOrganizer;
-    private int mTaskFragmentOrganizerUid = INVALID_UID;
+    @VisibleForTesting
+    int mTaskFragmentOrganizerUid = INVALID_UID;
     private @Nullable String mTaskFragmentOrganizerProcessName;
 
     /** Client assigned unique token for this TaskFragment if this is created by an organizer. */
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index e7a1cf1..707f9fc 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -762,6 +762,8 @@
                     .setTask(task)
                     .build());
         }
+        // Make sure the parent info changed event will be dispatched if there are no other changes.
+        mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
     }
 
     boolean isSystemOrganizer(@NonNull IBinder organizerToken) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index caa57bb..e5604ec 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1737,8 +1737,8 @@
         // Since we created root-leash but no longer reference it from core, release it now
         info.releaseAnimSurfaces();
 
-        mLogger.logOnSendAsync(mController.mLoggerHandler);
         if (mLogger.mInfo != null) {
+            mLogger.logOnSendAsync(mController.mLoggerHandler);
             mController.mTransitionTracer.logSentTransition(this, mTargets);
         }
     }
@@ -1756,6 +1756,27 @@
     }
 
     /**
+     * Checks if the transition contains order changes.
+     *
+     * This is a shallow check that doesn't account for collection in parallel, unlike
+     * {@code collectOrderChanges}
+     */
+    boolean hasOrderChanges() {
+        ArrayList<Task> onTopTasks = new ArrayList<>();
+        // Iterate over target displays to get up to date on top tasks.
+        // Cannot use `mOnTopTasksAtReady` as it's not populated before the `applyReady` is called.
+        for (DisplayContent dc : mTargetDisplays) {
+            addOnTopTasks(dc, onTopTasks);
+        }
+        for (Task task : onTopTasks) {
+            if (!mOnTopTasksStart.contains(task)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Collect tasks which moved-to-top as part of this transition. This also updates the
      * controller's latest-reported when relevant.
      *
@@ -3305,7 +3326,6 @@
          */
         void addGroup(WindowContainer wc) {
             if (mReadyGroups.containsKey(wc)) {
-                Slog.e(TAG, "Trying to add a ready-group twice: " + wc);
                 return;
             }
             mReadyGroups.put(wc, false);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index bacfda5..e648d64 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -792,6 +792,12 @@
         mCollectingTransition.recordTaskOrder(wc);
     }
 
+    /** @see Transition#hasOrderChanges */
+    boolean hasOrderChanges() {
+        if (mCollectingTransition == null) return false;
+        return mCollectingTransition.hasOrderChanges();
+    }
+
     /**
      * Collects the window containers which need to be synced with the changing display area into
      * the current collecting transition.
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 208df6c7..2af6569 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -23,7 +23,9 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE;
 import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
@@ -1468,6 +1470,16 @@
                 }
                 break;
             }
+            case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: {
+                final Task task = taskFragment.getTask();
+                task.moveOrCreateDecorSurfaceFor(taskFragment);
+                break;
+            }
+            case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: {
+                final Task task = taskFragment.getTask();
+                task.removeDecorSurface();
+                break;
+            }
         }
         return effects;
     }
@@ -1507,6 +1519,19 @@
             return false;
         }
 
+        // TODO (b/293654166) remove the decor surface checks once we clear security reviews
+        if ((opType == OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE
+                || opType == OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
+                && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+            final Throwable exception = new SecurityException(
+                    "Only a system organizer can perform OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE"
+                            + " or OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE."
+            );
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+                    opType, exception);
+            return false;
+        }
+
         final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();
         return secondaryFragmentToken == null
                 || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType,
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 cca4261..c625b1e 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -159,6 +159,12 @@
                 <xs:element type="usiVersion" name="usiVersion">
                     <xs:annotation name="final"/>
                 </xs:element>
+                <!-- Maximum screen brightness setting when screen brightness capped in
+                Wear Bedtime mode. This must be a non-negative decimal within the range defined by
+                the first and the last brightness value in screenBrightnessMap. -->
+                <xs:element type="nonNegativeDecimal" name="screenBrightnessCapForWearBedtimeMode">
+                    <xs:annotation name="final"/>
+                </xs:element>
             </xs:sequence>
         </xs:complexType>
     </xs:element>
@@ -586,42 +592,39 @@
                         minOccurs="0" maxOccurs="1">
                 <xs:annotation name="final"/>
             </xs:element>
-            <!-- Sets the brightness mapping of the desired screen brightness in nits to the
-             corresponding lux for the current display -->
-            <xs:element name="displayBrightnessMapping" type="displayBrightnessMapping"
+            <!-- Sets the brightness mapping of the desired screen brightness to the corresponding
+            lux for the current display -->
+            <xs:element name="luxToBrightnessMapping" type="luxToBrightnessMapping"
                         minOccurs="0" maxOccurs="1">
                 <xs:annotation name="final"/>
             </xs:element>
         </xs:sequence>
     </xs:complexType>
 
-    <!-- Represents the brightness mapping of the desired screen brightness in nits to the
-             corresponding lux for the current display -->
-    <xs:complexType name="displayBrightnessMapping">
-        <xs:sequence>
-            <!-- Sets the list of display brightness points, each representing the desired screen
-            brightness in nits to the corresponding lux for the current display
+    <!-- Sets the list of display brightness points, each representing the desired screen brightness
+    in a certain lux environment.
 
-            The N entries of this array define N + 1 control points as follows:
-            (1-based arrays)
+    The first value of each point is the lux value and the second value is the brightness value.
 
-            Point 1:            (0, nits[1]):             currentLux <= 0
-            Point 2:     (lux[1], nits[2]):       0 < currentLux <= lux[1]
-            Point 3:     (lux[2], nits[3]):  lux[2] < currentLux <= lux[3]
-            ...
-            Point N+1: (lux[N], nits[N+1]):            lux[N] < currentLux
+    The first lux value must be 0.
 
-            The control points must be strictly increasing. Each control point
-            corresponds to an entry in the brightness backlight values arrays.
-            For example, if currentLux == lux[1] (first element of the levels array)
-            then the brightness will be determined by nits[2] (second element
-            of the brightness values array).
-            -->
-            <xs:element name="displayBrightnessPoint" type="displayBrightnessPoint"
-                        minOccurs="1" maxOccurs="unbounded">
-                <xs:annotation name="final"/>
-            </xs:element>
-        </xs:sequence>
+    The control points must be strictly increasing.
+
+    Example: if currentLux == the second lux value in the mapping then the brightness will be
+    determined by the second brightness value in the mapping. Spline interpolation is used
+    to determine the auto-brightness values for lux levels between these control points.
+
+    The brightness values must be non-negative decimals within the range between the first and
+    the last brightness values in screenBrightnessMap.
+
+    This is used in place of config_autoBrightnessLevels and config_autoBrightnessLcdBacklightValues
+    defined in the config XML resource.
+    -->
+    <xs:complexType name="luxToBrightnessMapping">
+        <xs:element name="map" type="nonNegativeFloatToFloatMap">
+            <xs:annotation name="nonnull"/>
+            <xs:annotation name="final"/>
+        </xs:element>
     </xs:complexType>
 
     <!-- Represents a point in the display brightness mapping, representing the lux level from the
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index f767291..8c8c123 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -7,14 +7,14 @@
     method public final java.math.BigInteger getBrighteningLightDebounceMillis();
     method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis();
     method public final java.math.BigInteger getDarkeningLightDebounceMillis();
-    method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
     method public boolean getEnabled();
+    method public final com.android.server.display.config.LuxToBrightnessMapping getLuxToBrightnessMapping();
     method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger);
     method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger);
     method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
-    method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
     method public void setEnabled(boolean);
+    method public final void setLuxToBrightnessMapping(com.android.server.display.config.LuxToBrightnessMapping);
   }
 
   public class BlockingZoneConfig {
@@ -78,11 +78,6 @@
     method public java.util.List<com.android.server.display.config.Density> getDensity();
   }
 
-  public class DisplayBrightnessMapping {
-    ctor public DisplayBrightnessMapping();
-    method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
-  }
-
   public class DisplayBrightnessPoint {
     ctor public DisplayBrightnessPoint();
     method public final java.math.BigInteger getLux();
@@ -110,6 +105,7 @@
     method public final com.android.server.display.config.SensorDetails getProxSensor();
     method public com.android.server.display.config.DisplayQuirks getQuirks();
     method public com.android.server.display.config.RefreshRateConfigs getRefreshRate();
+    method public final java.math.BigDecimal getScreenBrightnessCapForWearBedtimeMode();
     method @NonNull public final java.math.BigDecimal getScreenBrightnessDefault();
     method @NonNull public final com.android.server.display.config.NitsMap getScreenBrightnessMap();
     method public final java.math.BigInteger getScreenBrightnessRampDecreaseMaxIdleMillis();
@@ -143,6 +139,7 @@
     method public final void setProxSensor(com.android.server.display.config.SensorDetails);
     method public void setQuirks(com.android.server.display.config.DisplayQuirks);
     method public void setRefreshRate(com.android.server.display.config.RefreshRateConfigs);
+    method public final void setScreenBrightnessCapForWearBedtimeMode(java.math.BigDecimal);
     method public final void setScreenBrightnessDefault(@NonNull java.math.BigDecimal);
     method public final void setScreenBrightnessMap(@NonNull com.android.server.display.config.NitsMap);
     method public final void setScreenBrightnessRampDecreaseMaxIdleMillis(java.math.BigInteger);
@@ -220,6 +217,12 @@
     method @NonNull public final java.util.List<com.android.server.display.config.BrightnessLimitMap> getBrightnessLimitMap();
   }
 
+  public class LuxToBrightnessMapping {
+    ctor public LuxToBrightnessMapping();
+    method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
+    method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+  }
+
   public class NitsMap {
     ctor public NitsMap();
     method public String getInterpolation();
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 1988bb6..da44aac 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -23,12 +23,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.credentials.CredentialProviderInfo;
+import android.credentials.flags.Flags;
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.UserSelectionDialogResult;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IInterface;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -94,6 +96,9 @@
 
     private final Set<ComponentName> mEnabledProviders;
 
+    private final RequestSessionDeathRecipient mDeathRecipient =
+            new RequestSessionDeathRecipient();
+
     protected PendingIntent mPendingIntent;
 
     @NonNull
@@ -141,11 +146,26 @@
         mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted,
                 mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType));
         setCancellationListener();
+        if (Flags.clearSessionEnabled()) {
+            setUpClientCallbackListener();
+        }
+    }
+
+    private void setUpClientCallbackListener() {
+        if (mClientCallback != null && mClientCallback instanceof IInterface) {
+            IInterface callback = (IInterface) mClientCallback;
+            try {
+                callback.asBinder().linkToDeath(mDeathRecipient, 0);
+            } catch (RemoteException e) {
+                Slog.e(TAG, e.getMessage());
+            }
+        }
     }
 
     private void setCancellationListener() {
         mCancellationSignal.setOnCancelListener(
                 () -> {
+                    Slog.d(TAG, "Cancellation invoked from the client - clearing session");
                     boolean isUiActive = maybeCancelUi();
                     finishSession(!isUiActive);
                 }
@@ -168,6 +188,17 @@
         return false;
     }
 
+    private boolean isUiWaitingForData() {
+        // Technically, the status can also be IN_PROGRESS when the user has made a selection
+        // so this an over estimation, but safe to do so as it is used for cancellation
+        // propagation to the provider in a very narrow time frame. If provider has
+        // already responded, cancellation is not an issue as the cancellation listener
+        // is independent of the service binding.
+        // TODO(b/313512500): Do not propagate cancelation if provider has responded in
+        // query phase.
+        return mCredentialManagerUi.getStatus() == CredentialManagerUi.UiStatus.IN_PROGRESS;
+    }
+
     public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
             RemoteCredentialService remoteCredentialService);
 
@@ -373,4 +404,12 @@
         return chosenProviderSession != null && chosenProviderSession.mProviderInfo != null
                 && chosenProviderSession.mProviderInfo.isPrimary();
     }
+
+    private class RequestSessionDeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            Slog.d(TAG, "Client binder died - clearing session");
+            finishSession(isUiWaitingForData());
+        }
+    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0a2e806..1919eb3 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2112,6 +2112,15 @@
             networkPolicy.bindConnectivityManager();
             t.traceEnd();
 
+            t.traceBegin("StartSecurityStateManagerService");
+            try {
+                ServiceManager.addService(Context.SECURITY_STATE_SERVICE,
+                        new SecurityStateManagerService(context));
+            } catch (Throwable e) {
+                reportWtf("starting SecurityStateManagerService", e);
+            }
+            t.traceEnd();
+
             t.traceBegin("StartVpnManagerService");
             try {
                 vpnManager = VpnManagerService.create(context);
diff --git a/services/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java b/services/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java
deleted file mode 100644
index 0ad4184..0000000
--- a/services/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.when;
-
-import android.app.Application;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.media.MediaRoute2Info;
-import android.os.UserHandle;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.Shadows;
-import org.robolectric.shadows.ShadowBluetoothAdapter;
-import org.robolectric.shadows.ShadowBluetoothDevice;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-@RunWith(RobolectricTestRunner.class)
-public class AudioPoliciesBluetoothRouteControllerTest {
-
-    private static final String DEVICE_ADDRESS_UNKNOWN = ":unknown:ip:address:";
-    private static final String DEVICE_ADDRESS_SAMPLE_1 = "30:59:8B:E4:C6:35";
-    private static final String DEVICE_ADDRESS_SAMPLE_2 = "0D:0D:A6:FF:8D:B6";
-    private static final String DEVICE_ADDRESS_SAMPLE_3 = "2D:9B:0C:C2:6F:78";
-    private static final String DEVICE_ADDRESS_SAMPLE_4 = "66:88:F9:2D:A8:1E";
-
-    private Context mContext;
-
-    private ShadowBluetoothAdapter mShadowBluetoothAdapter;
-
-    @Mock
-    private BluetoothRouteController.BluetoothRoutesUpdatedListener mListener;
-
-    @Mock
-    private BluetoothProfileMonitor mBluetoothProfileMonitor;
-
-    private AudioPoliciesBluetoothRouteController mAudioPoliciesBluetoothRouteController;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        Application application = ApplicationProvider.getApplicationContext();
-        mContext = application;
-
-        BluetoothManager bluetoothManager = (BluetoothManager)
-                mContext.getSystemService(Context.BLUETOOTH_SERVICE);
-
-        BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
-        mShadowBluetoothAdapter = Shadows.shadowOf(bluetoothAdapter);
-
-        mAudioPoliciesBluetoothRouteController =
-                new AudioPoliciesBluetoothRouteController(mContext, bluetoothAdapter,
-                        mBluetoothProfileMonitor, mListener) {
-                    @Override
-                    boolean isDeviceConnected(BluetoothDevice device) {
-                        return true;
-                    }
-                };
-
-        // Enable A2DP profile.
-        when(mBluetoothProfileMonitor.isProfileSupported(eq(BluetoothProfile.A2DP), any()))
-                .thenReturn(true);
-        mShadowBluetoothAdapter.setProfileConnectionState(BluetoothProfile.A2DP,
-                BluetoothProfile.STATE_CONNECTED);
-
-        mAudioPoliciesBluetoothRouteController.start(UserHandle.of(0));
-    }
-
-    @Test
-    public void getSelectedRoute_noBluetoothRoutesAvailable_returnsNull() {
-        assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
-    }
-
-    @Test
-    public void selectRoute_noBluetoothRoutesAvailable_returnsFalse() {
-        assertThat(mAudioPoliciesBluetoothRouteController
-                .selectRoute(DEVICE_ADDRESS_UNKNOWN)).isFalse();
-    }
-
-    @Test
-    public void selectRoute_noDeviceWithGivenAddress_returnsFalse() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_3);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        assertThat(mAudioPoliciesBluetoothRouteController
-                .selectRoute(DEVICE_ADDRESS_SAMPLE_2)).isFalse();
-    }
-
-    @Test
-    public void selectRoute_deviceIsInDevicesSet_returnsTrue() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        assertThat(mAudioPoliciesBluetoothRouteController
-                .selectRoute(DEVICE_ADDRESS_SAMPLE_1)).isTrue();
-    }
-
-    @Test
-    public void selectRoute_resetSelectedDevice_returnsTrue() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_1);
-        assertThat(mAudioPoliciesBluetoothRouteController.selectRoute(null)).isTrue();
-    }
-
-    @Test
-    public void selectRoute_noSelectedDevice_returnsTrue() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        assertThat(mAudioPoliciesBluetoothRouteController.selectRoute(null)).isTrue();
-    }
-
-    @Test
-    public void getSelectedRoute_updateRouteFailed_returnsNull() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-        mAudioPoliciesBluetoothRouteController
-                .selectRoute(DEVICE_ADDRESS_SAMPLE_3);
-
-        assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
-    }
-
-    @Test
-    public void getSelectedRoute_updateRouteSuccessful_returnsUpdateDevice() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4);
-
-        assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        assertThat(mAudioPoliciesBluetoothRouteController
-                .selectRoute(DEVICE_ADDRESS_SAMPLE_4)).isTrue();
-
-        MediaRoute2Info selectedRoute = mAudioPoliciesBluetoothRouteController.getSelectedRoute();
-        assertThat(selectedRoute.getAddress()).isEqualTo(DEVICE_ADDRESS_SAMPLE_4);
-    }
-
-    @Test
-    public void getSelectedRoute_resetSelectedRoute_returnsNull() {
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
-                DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4);
-
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        // Device is not null now.
-        mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
-        // Rest the device.
-        mAudioPoliciesBluetoothRouteController.selectRoute(null);
-
-        assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute())
-                .isNull();
-    }
-
-    @Test
-    public void getTransferableRoutes_noSelectedRoute_returnsAllBluetoothDevices() {
-        String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
-                DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        // Force route controller to update bluetooth devices list.
-        sendBluetoothDevicesChangedBroadcast();
-
-        Set<String> transferableDevices = extractAddressesListFrom(
-                mAudioPoliciesBluetoothRouteController.getTransferableRoutes());
-        assertThat(transferableDevices).containsExactlyElementsIn(addresses);
-    }
-
-    @Test
-    public void getTransferableRoutes_hasSelectedRoute_returnsRoutesWithoutSelectedDevice() {
-        String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
-                DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        // Force route controller to update bluetooth devices list.
-        sendBluetoothDevicesChangedBroadcast();
-        mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
-
-        Set<String> transferableDevices = extractAddressesListFrom(
-                mAudioPoliciesBluetoothRouteController.getTransferableRoutes());
-        assertThat(transferableDevices).containsExactly(DEVICE_ADDRESS_SAMPLE_1,
-                DEVICE_ADDRESS_SAMPLE_2);
-    }
-
-    @Test
-    public void getAllBluetoothRoutes_hasSelectedRoute_returnsAllRoutes() {
-        String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
-                DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        // Force route controller to update bluetooth devices list.
-        sendBluetoothDevicesChangedBroadcast();
-        mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
-
-        Set<String> bluetoothDevices = extractAddressesListFrom(
-                mAudioPoliciesBluetoothRouteController.getAllBluetoothRoutes());
-        assertThat(bluetoothDevices).containsExactlyElementsIn(addresses);
-    }
-
-    @Test
-    public void updateVolumeForDevice_setVolumeForA2DPTo25_selectedRouteVolumeIsUpdated() {
-        String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
-                DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
-        Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
-        mShadowBluetoothAdapter.setBondedDevices(devices);
-
-        // Force route controller to update bluetooth devices list.
-        sendBluetoothDevicesChangedBroadcast();
-        mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
-
-        mAudioPoliciesBluetoothRouteController.updateVolumeForDevices(
-                AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, 25);
-
-        MediaRoute2Info selectedRoute = mAudioPoliciesBluetoothRouteController.getSelectedRoute();
-        assertThat(selectedRoute.getVolume()).isEqualTo(25);
-    }
-
-    private void sendBluetoothDevicesChangedBroadcast() {
-        Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
-        mContext.sendBroadcast(intent);
-    }
-
-    private static Set<String> extractAddressesListFrom(Collection<MediaRoute2Info> routes) {
-        Set<String> addresses = new HashSet<>();
-
-        for (MediaRoute2Info route: routes) {
-            addresses.add(route.getAddress());
-        }
-
-        return addresses;
-    }
-
-    private static Set<BluetoothDevice> generateFakeBluetoothDevicesSet(String... addresses) {
-        Set<BluetoothDevice> devices = new HashSet<>();
-
-        for (String address: addresses) {
-            devices.add(ShadowBluetoothDevice.newInstance(address));
-        }
-
-        return devices;
-    }
-}
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 97e5826..a2e80f0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -104,7 +104,7 @@
         468.5f,
     };
 
-    private static final int[] DISPLAY_LEVELS_BACKLIGHT = {
+    private static final int[] DISPLAY_LEVELS_INT = {
         9,
         30,
         45,
@@ -118,6 +118,20 @@
         255
     };
 
+    private static final float[] DISPLAY_LEVELS = {
+        0.03f,
+        0.11f,
+        0.17f,
+        0.24f,
+        0.3f,
+        0.37f,
+        0.46f,
+        0.57f,
+        0.7f,
+        0.87f,
+        1
+    };
+
     private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
     private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
     private static final float[] DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT = { 0.03149606299f, 1.0f };
@@ -155,23 +169,23 @@
     DisplayWhiteBalanceController mMockDwbc;
 
     @Test
-    public void testSimpleStrategyMappingAtControlPoints() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+    public void testSimpleStrategyMappingAtControlPoints_IntConfig() {
+        Resources res = createResources(DISPLAY_LEVELS_INT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
         for (int i = 0; i < LUX_LEVELS.length; i++) {
             final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1,
                     PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN,
-                    PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_BACKLIGHT[i]);
+                    PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_INT[i]);
             assertEquals(expectedLevel,
                     simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/);
         }
     }
 
     @Test
-    public void testSimpleStrategyMappingBetweenControlPoints() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+    public void testSimpleStrategyMappingBetweenControlPoints_IntConfig() {
+        Resources res = createResources(DISPLAY_LEVELS_INT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
@@ -179,14 +193,42 @@
             final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
             final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
             assertTrue("Desired brightness should be between adjacent control points.",
-                    backlight > DISPLAY_LEVELS_BACKLIGHT[i - 1]
-                            && backlight < DISPLAY_LEVELS_BACKLIGHT[i]);
+                    backlight > DISPLAY_LEVELS_INT[i - 1]
+                            && backlight < DISPLAY_LEVELS_INT[i]);
+        }
+    }
+
+    @Test
+    public void testSimpleStrategyMappingAtControlPoints_FloatConfig() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS,
+                EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS);
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        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= */ 0.0001f);
+        }
+    }
+
+    @Test
+    public void testSimpleStrategyMappingBetweenControlPoints_FloatConfig() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS,
+                EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS);
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        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;
+            final float brightness = simple.getBrightness(lux);
+            assertTrue("Desired brightness should be between adjacent control points.",
+                    brightness > DISPLAY_LEVELS[i - 1] && brightness < DISPLAY_LEVELS[i]);
         }
     }
 
     @Test
     public void testSimpleStrategyIgnoresNewConfiguration() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        Resources res = createResources(DISPLAY_LEVELS_INT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
 
@@ -201,14 +243,14 @@
 
     @Test
     public void testSimpleStrategyIgnoresNullConfiguration() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        Resources res = createResources(DISPLAY_LEVELS_INT);
         DisplayDeviceConfig ddc = createDdc();
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
 
         strategy.setBrightnessConfiguration(null);
-        final int n = DISPLAY_LEVELS_BACKLIGHT.length;
+        final int n = DISPLAY_LEVELS_INT.length;
         final float expectedBrightness =
-                (float) DISPLAY_LEVELS_BACKLIGHT[n - 1] / PowerManager.BRIGHTNESS_ON;
+                (float) DISPLAY_LEVELS_INT[n - 1] / PowerManager.BRIGHTNESS_ON;
         assertEquals(expectedBrightness,
                 strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
     }
@@ -322,7 +364,7 @@
 
     @Test
     public void testDefaultStrategyIsPhysical() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        Resources res = createResources(DISPLAY_LEVELS_INT);
         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
@@ -363,13 +405,13 @@
         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNull(strategy);
 
-        res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        res = createResources(DISPLAY_LEVELS_INT);
         strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
         assertNull(strategy);
 
         // Extra backlight level
         final int[] backlight = Arrays.copyOf(
-                DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length + 1);
+                DISPLAY_LEVELS_INT, DISPLAY_LEVELS_INT.length + 1);
         backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
         res = createResources(backlight);
         ddc = createDdc(DISPLAY_RANGE_NITS,
@@ -410,7 +452,7 @@
                 LUX_LEVELS, DISPLAY_LEVELS_NITS);
         assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
         ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
-        res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        res = createResources(DISPLAY_LEVELS_INT);
         assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
     }
 
@@ -546,16 +588,24 @@
         when(mockDdc.getBrightness()).thenReturn(backlightArray);
         when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
         when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
+        when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY);
         return mockDdc;
     }
 
     private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
             float[] luxLevelsFloat, float[] brightnessLevelsNits) {
+        return createDdc(nitsArray, backlightArray, luxLevelsFloat, brightnessLevelsNits,
+                EMPTY_FLOAT_ARRAY);
+    }
+
+    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
+            float[] luxLevelsFloat, float[] brightnessLevelsNits, float[] brightnessLevels) {
         DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
         when(mockDdc.getNits()).thenReturn(nitsArray);
         when(mockDdc.getBrightness()).thenReturn(backlightArray);
         when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevelsFloat);
         when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits);
+        when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels);
         return mockDdc;
     }
 
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 0bcbeb9..31d7e88 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -47,8 +47,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.config.HdrBrightnessData;
 import com.android.server.display.config.ThermalStatus;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -92,10 +94,14 @@
     @Mock
     private Resources mResources;
 
+    @Mock
+    private DisplayManagerFlags mFlags;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mContext.getResources()).thenReturn(mResources);
+        when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(true);
         mockDeviceConfigs();
     }
 
@@ -110,10 +116,6 @@
         assertArrayEquals(mDisplayDeviceConfig.getBrightness(), BRIGHTNESS, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getNits(), NITS, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getBacklight(), BRIGHTNESS, ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
-                float[]{45.32f, 75.43f}, ZERO_DELTA);
 
         // Test thresholds
         assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(),
@@ -606,7 +608,7 @@
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
                 float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+                float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
 
         // Test thresholds
         assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
@@ -671,6 +673,9 @@
 
         assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type);
         assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
+
+        assertEquals(BrightnessSynchronizer.brightnessIntToFloat(35),
+                mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
     }
 
     @Test
@@ -716,6 +721,38 @@
         assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncreaseIdle(), 0.04f, ZERO_DELTA);
     }
 
+    @Test
+    public void testBrightnessCapForWearBedtimeMode() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
+                getValidProxSensor(), /* includeIdleMode= */ false));
+        assertEquals(0.1f, mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
+    }
+
+    @Test
+    public void testAutoBrightnessBrighteningLevels() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
+                getValidProxSensor(), /* includeIdleMode= */ false));
+
+        assertArrayEquals(new float[]{0.0f, 80},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.2f, 0.3f},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA);
+    }
+
+    @Test
+    public void testAutoBrightnessBrighteningLevels_FeatureFlagOff() throws IOException {
+        when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(false);
+        setupDisplayDeviceConfigFromConfigResourceFile();
+        setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
+                getValidProxSensor(), /* includeIdleMode= */ false));
+
+        assertNull(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels());
+        assertArrayEquals(new float[]{0, 110, 500},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
+        assertArrayEquals(new float[]{2, 200, 600},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA);
+    }
+
     private String getValidLuxThrottling() {
         return "<luxThrottling>\n"
                 + "    <brightnessLimitMap>\n"
@@ -1037,8 +1074,8 @@
                 +   "<screenBrightnessRampDecreaseMaxIdleMillis>"
                 +       "5000"
                 +   "</screenBrightnessRampDecreaseMaxIdleMillis>\n";
-
     }
+
     private String getContent() {
         return getContent(getValidLuxThrottling(), getValidProxSensor(),
                 /* includeIdleMode= */ true);
@@ -1089,16 +1126,18 @@
                 +       "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
                 +       "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
                 + (includeIdleMode ? getRampSpeedsIdle() : "")
-                +       "<displayBrightnessMapping>\n"
-                +            "<displayBrightnessPoint>\n"
-                +                "<lux>50</lux>\n"
-                +                "<nits>45.32</nits>\n"
-                +            "</displayBrightnessPoint>\n"
-                +            "<displayBrightnessPoint>\n"
-                +                "<lux>80</lux>\n"
-                +                "<nits>75.43</nits>\n"
-                +            "</displayBrightnessPoint>\n"
-                +       "</displayBrightnessMapping>\n"
+                +       "<luxToBrightnessMapping>\n"
+                +           "<map>\n"
+                +               "<point>\n"
+                +                   "<first>0</first>\n"
+                +                   "<second>0.2</second>\n"
+                +               "</point>\n"
+                +               "<point>\n"
+                +                   "<first>80</first>\n"
+                +                   "<second>0.3</second>\n"
+                +               "</point>\n"
+                +           "</map>\n"
+                +       "</luxToBrightnessMapping>\n"
                 +   "</autoBrightness>\n"
                 +  getPowerThrottlingConfig()
                 +   "<highBrightnessMode enabled=\"true\">\n"
@@ -1355,6 +1394,9 @@
                 +       "<majorVersion>2</majorVersion>\n"
                 +       "<minorVersion>0</minorVersion>\n"
                 +   "</usiVersion>\n"
+                +   "<screenBrightnessCapForWearBedtimeMode>"
+                +       "0.1"
+                +   "</screenBrightnessCapForWearBedtimeMode>"
                 + "</displayConfiguration>\n";
     }
 
@@ -1372,7 +1414,7 @@
     private void setupDisplayDeviceConfigFromDisplayConfigFile(String content) throws IOException {
         Path tempFile = Files.createTempFile("display_config", ".tmp");
         Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
-        mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
+        mDisplayDeviceConfig = new DisplayDeviceConfig(mContext, mFlags);
         mDisplayDeviceConfig.initFromFile(tempFile.toFile());
     }
 
@@ -1381,25 +1423,15 @@
         when(mResources.obtainTypedArray(
                 com.android.internal.R.array.config_screenBrightnessNits))
                 .thenReturn(screenBrightnessNits);
-        TypedArray screenBrightnessBacklight = createFloatTypedArray(new
-                float[]{0.0f, 120.0f, 255.0f});
-        when(mResources.obtainTypedArray(
-                com.android.internal.R.array.config_screenBrightnessBacklight))
-                .thenReturn(screenBrightnessBacklight);
         when(mResources.getIntArray(com.android.internal.R.array
                 .config_screenBrightnessBacklight)).thenReturn(new int[]{0, 120, 255});
 
-        when(mResources.getIntArray(com.android.internal.R.array
-                .config_autoBrightnessLevels)).thenReturn(new int[]{30, 80});
-        when(mResources.getIntArray(com.android.internal.R.array
-                .config_autoBrightnessDisplayValuesNits)).thenReturn(new int[]{25, 55});
-
         TypedArray screenBrightnessLevelNits = createFloatTypedArray(new
                 float[]{2.0f, 200.0f, 600.0f});
         when(mResources.obtainTypedArray(
                 com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
                 .thenReturn(screenBrightnessLevelNits);
-        int[] screenBrightnessLevelLux = new int[]{0, 110, 500};
+        int[] screenBrightnessLevelLux = new int[]{110, 500};
         when(mResources.getIntArray(
                 com.android.internal.R.array.config_autoBrightnessLevels))
                 .thenReturn(screenBrightnessLevelLux);
@@ -1457,7 +1489,12 @@
                 R.integer.config_autoBrightnessDarkeningLightDebounce))
                 .thenReturn(4000);
 
-        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+        when(mResources.getInteger(
+                R.integer.config_screenBrightnessCapForWearBedtimeMode))
+                .thenReturn(35);
+
+        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, /* useConfigXml= */ true,
+                mFlags);
     }
 
     private TypedArray createFloatTypedArray(float[] vals) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
index 4fd8f26..dc6abf1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -57,6 +57,9 @@
     @Mock
     private SurfaceControl.Transaction mMockTransaction;
 
+    @Mock
+    private DisplayAdapter mMockDisplayAdapter;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -67,34 +70,39 @@
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
-        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter);
         assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
     }
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation0() {
-        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter);
         displayDevice.setProjectionLocked(mMockTransaction, ROTATION_0, new Rect(), new Rect());
         assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
     }
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
-        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter);
         displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
         assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE);
     }
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation180() {
-        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter);
         displayDevice.setProjectionLocked(mMockTransaction, ROTATION_180, new Rect(), new Rect());
         assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
     }
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation270() {
-        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter);
         displayDevice.setProjectionLocked(mMockTransaction, ROTATION_270, new Rect(), new Rect());
         assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE);
     }
@@ -102,8 +110,9 @@
     private static class FakeDisplayDevice extends DisplayDevice {
         private final DisplayDeviceInfo mDisplayDeviceInfo;
 
-        FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo) {
-            super(null, null, "", InstrumentationRegistry.getInstrumentation().getContext());
+        FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter) {
+            super(displayAdapter, /* displayToken= */ null, /* uniqueId= */ "",
+                    InstrumentationRegistry.getInstrumentation().getContext());
             mDisplayDeviceInfo = displayDeviceInfo;
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 55f56e9..02e3ef4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -212,7 +212,8 @@
             new DisplayManagerService.Injector() {
                 @Override
                 VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
-                        Context context, Handler handler, DisplayAdapter.Listener listener) {
+                        Context context, Handler handler, DisplayAdapter.Listener listener,
+                        DisplayManagerFlags flags) {
                     return mMockVirtualDisplayAdapter;
                 }
 
@@ -251,7 +252,8 @@
 
         @Override
         VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
-                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+                Handler handler, DisplayAdapter.Listener displayAdapterListener,
+                DisplayManagerFlags flags) {
             return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
                     new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
                         @Override
@@ -263,7 +265,7 @@
                         @Override
                         public void destroyDisplay(IBinder displayToken) {
                         }
-                    });
+                    }, flags);
         }
 
         @Override
@@ -329,6 +331,7 @@
     @Mock SensorManager mSensorManager;
     @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
     @Mock PackageManagerInternal mMockPackageManagerInternal;
+    @Mock DisplayAdapter mMockDisplayAdapter;
 
     @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
     @Mock DisplayManagerFlags mMockFlags;
@@ -788,7 +791,8 @@
                 new DisplayManagerService.Injector() {
                     @Override
                     VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
-                            Context context, Handler handler, DisplayAdapter.Listener listener) {
+                            Context context, Handler handler, DisplayAdapter.Listener listener,
+                            DisplayManagerFlags flags) {
                         return null;  // return null for the adapter.  This should cause a failure.
                     }
 
@@ -3194,7 +3198,7 @@
         private DisplayDeviceInfo mDisplayDeviceInfo;
 
         FakeDisplayDevice() {
-            super(null, null, "", mContext);
+            super(mMockDisplayAdapter, /* displayToken= */ null, /* uniqueId= */ "", mContext);
         }
 
         public void setDisplayDeviceInfo(DisplayDeviceInfo displayDeviceInfo) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 8270845..f36854b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -1455,8 +1455,8 @@
         // mMockContext and values will be loaded from mMockResources.
         @Override
         public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
-                long physicalDisplayId, boolean isFirstDisplay) {
-            return DisplayDeviceConfig.create(context, isFirstDisplay);
+                long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
+            return DisplayDeviceConfig.create(context, isFirstDisplay, flags);
         }
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 43d2b8f..28ec896 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -119,8 +119,8 @@
     @Mock IThermalService mIThermalServiceMock;
     @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
             new DeviceStateToLayoutMap(mIdProducer, NON_EXISTING_FILE);
-    @Mock
-    DisplayManagerFlags mFlagsMock;
+    @Mock DisplayManagerFlags mFlagsMock;
+    @Mock DisplayAdapter mDisplayAdapterMock;
 
     @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
     @Captor ArgumentCaptor<Integer> mDisplayEventCaptor;
@@ -1075,7 +1075,8 @@
         private int mState;
 
         TestDisplayDevice() {
-            super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock);
+            super(mDisplayAdapterMock, /* displayToken= */ null,
+                    "test_display_" + sUniqueTestDisplayId++, mContextMock);
             mInfo = new DisplayDeviceInfo();
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
index 9f91916..5676a38 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -32,9 +32,12 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -51,8 +54,12 @@
     private TestInjector mInjector;
     private TestLooper mTestLooper;
 
+    @Mock
+    private DisplayAdapter mDisplayAdapter;
+
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         mInjector = new TestInjector();
         mTestLooper = new TestLooper();
         Handler handler = new Handler(mTestLooper.getLooper());
@@ -62,8 +69,8 @@
     @Test
     public void testLoadBrightness() {
         final String uniqueDisplayId = "test:123";
-        final DisplayDevice testDisplayDevice = new DisplayDevice(
-                null, null, uniqueDisplayId, null) {
+        final DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
@@ -100,7 +107,8 @@
     public void testSetBrightness_brightnessTagWithNoUserId_updatesToBrightnessTagWithUserId() {
         final String uniqueDisplayId = "test:123";
         final DisplayDevice testDisplayDevice =
-                new DisplayDevice(null, null, uniqueDisplayId, null) {
+                new DisplayDevice(mDisplayAdapter, /* displayToken= */ null, uniqueDisplayId,
+                        /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
@@ -273,7 +281,8 @@
         assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
                 userSerial));
 
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+        DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
@@ -319,7 +328,8 @@
         assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
                 userSerial));
 
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+        DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return false;
@@ -386,7 +396,8 @@
     @Test
     public void testStoreAndRestoreResolution() {
         final String uniqueDisplayId = "test:123";
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+        DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
@@ -422,7 +433,8 @@
     @Test
     public void testStoreAndRestoreRefreshRate() {
         final String uniqueDisplayId = "test:123";
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+        DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
@@ -455,7 +467,8 @@
     @Test
     public void testBrightnessInitialisesWithInvalidFloat() {
         final String uniqueDisplayId = "test:123";
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+        DisplayDevice testDisplayDevice = new DisplayDevice(mDisplayAdapter,
+                /* displayToken= */ null, uniqueDisplayId, /* context= */ null) {
             @Override
             public boolean hasStableUniqueId() {
                 return true;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
index b7cbac5..5c50acb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
@@ -72,27 +72,14 @@
 
     @Test
     public void testFindHighestRefreshRateForDefaultDisplay() {
-        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
-        assertEquals(120,
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
-                /* delta= */ 0);
-    }
-
-    @Test
-    public void testFindHighestRefreshRate() {
-        int displayId = 13;
-        when(mDisplayManagerMock.getDisplay(displayId)).thenReturn(mDisplayMock);
-        assertEquals(120,
-                RefreshRateSettingsUtils.findHighestRefreshRate(mContext, displayId),
-                /* delta= */ 0);
-    }
-
-    @Test
-    public void testFindHighestRefreshRate_DisplayIsNull() {
         when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null);
         assertEquals(DEFAULT_REFRESH_RATE,
                 RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
                 /* delta= */ 0);
 
+        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
+        assertEquals(120,
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
+                /* delta= */ 0);
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
index 73e7ba0..c01b15c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -28,6 +28,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.testutils.TestHandler;
 
 import org.junit.Before;
@@ -59,13 +60,17 @@
 
     private VirtualDisplayAdapter mVirtualDisplayAdapter;
 
+    @Mock
+    private DisplayManagerFlags mFeatureFlags;
+
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mHandler = new TestHandler(null);
         mVirtualDisplayAdapter = new VirtualDisplayAdapter(new DisplayManagerService.SyncRoot(),
-                mContextMock, mHandler, mMockListener, mMockSufaceControlDisplayFactory);
+                mContextMock, mHandler, mMockListener, mMockSufaceControlDisplayFactory,
+                mFeatureFlags);
 
         when(mMockCallback.asBinder()).thenReturn(mMockBinder);
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index ff2b1f4..9aa6136 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -262,7 +262,8 @@
                 Handler handler,
                 BrightnessClamperController.ClamperChangeListener clamperChangeListener,
                 BrightnessClamperController.DisplayDeviceData data,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags,
+                Context context) {
             mCapturedChangeListener = clamperChangeListener;
             return mClampers;
         }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
new file mode 100644
index 0000000..3458b08
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_OFF;
+import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_ON;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class BrightnessWearBedtimeModeClamperTest {
+
+    private static final float BRIGHTNESS_CAP = 0.3f;
+
+    @Mock
+    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    private final TestHandler mTestHandler = new TestHandler(null);
+    private final TestInjector mInjector = new TestInjector();
+    private BrightnessWearBedtimeModeClamper mClamper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mClamper = new BrightnessWearBedtimeModeClamper(mInjector, mTestHandler, mContext,
+                mMockClamperChangeListener, () -> BRIGHTNESS_CAP);
+        mTestHandler.flush();
+    }
+
+    @Test
+    public void testBrightnessCap() {
+        assertEquals(BRIGHTNESS_CAP, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON);
+    }
+
+    @Test
+    public void testBedtimeModeOn() {
+        setBedtimeModeEnabled(true);
+        assertTrue(mClamper.isActive());
+        verify(mMockClamperChangeListener).onChanged();
+    }
+
+    @Test
+    public void testBedtimeModeOff() {
+        setBedtimeModeEnabled(false);
+        assertFalse(mClamper.isActive());
+        verify(mMockClamperChangeListener).onChanged();
+    }
+
+    @Test
+    public void testType() {
+        assertEquals(BrightnessClamper.Type.BEDTIME_MODE, mClamper.getType());
+    }
+
+    @Test
+    public void testOnDisplayChanged() {
+        float newBrightnessCap = 0.61f;
+
+        mClamper.onDisplayChanged(() -> newBrightnessCap);
+        mTestHandler.flush();
+
+        assertEquals(newBrightnessCap, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON);
+        verify(mMockClamperChangeListener).onChanged();
+    }
+
+    private void setBedtimeModeEnabled(boolean enabled) {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE,
+                enabled ? BEDTIME_MODE_ON : BEDTIME_MODE_OFF);
+        mInjector.notifyBedtimeModeChanged();
+        mTestHandler.flush();
+    }
+
+    private static class TestInjector extends BrightnessWearBedtimeModeClamper.Injector {
+
+        private ContentObserver mObserver;
+
+        @Override
+        void registerBedtimeModeObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            mObserver = observer;
+        }
+
+        private void notifyBedtimeModeChanged() {
+            if (mObserver != null) {
+                mObserver.dispatchChange(/* selfChange= */ false,
+                        Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE));
+            }
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index d085923..60a0c03 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -290,7 +290,6 @@
     };
 
     private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
-    private static final int DISPLAY_ID_2 = Display.DEFAULT_DISPLAY + 1;
     private static final int MODE_ID = 1;
     private static final float TRANSITION_POINT = 0.763f;
 
@@ -1551,12 +1550,9 @@
     public void testPeakRefreshRate_FlagEnabled() {
         when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
                 .thenReturn(true);
-        float highestRefreshRate1 = 130;
-        float highestRefreshRate2 = 132;
-        doReturn(highestRefreshRate1).when(() ->
-                RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID));
-        doReturn(highestRefreshRate2).when(() ->
-                RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID_2));
+        float highestRefreshRate = 130;
+        doReturn(highestRefreshRate).when(() ->
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
@@ -1567,14 +1563,10 @@
 
         setPeakRefreshRate(Float.POSITIVE_INFINITY);
 
-        Vote vote1 = director.getVote(DISPLAY_ID,
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
                 Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        Vote vote2 = director.getVote(DISPLAY_ID_2,
-                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
-                /* frameRateHigh= */ highestRefreshRate1);
-        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
-                /* frameRateHigh= */ highestRefreshRate2);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                highestRefreshRate);
     }
 
     @Test
@@ -1592,54 +1584,19 @@
 
         setPeakRefreshRate(peakRefreshRate);
 
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
-                /* frameRateHigh= */ peakRefreshRate);
-    }
-
-    @Test
-    public void testPeakRefreshRate_DisplayChanged() {
-        when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
-                .thenReturn(true);
-        float highestRefreshRate = 130;
-        doReturn(highestRefreshRate).when(() ->
-                RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID));
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        Sensor lightSensor = createLightSensor();
-        SensorManager sensorManager = createMockSensorManager(lightSensor);
-        director.start(sensorManager);
-
-        setPeakRefreshRate(Float.POSITIVE_INFINITY);
-
-        Vote vote = director.getVote(DISPLAY_ID,
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
                 Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
-                /* frameRateHigh= */ highestRefreshRate);
-
-        // The highest refresh rate of the display changes
-        highestRefreshRate = 140;
-        doReturn(highestRefreshRate).when(() ->
-                RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID));
-        director.getDisplayObserver().onDisplayChanged(DISPLAY_ID);
-
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
-                /* frameRateHigh= */ highestRefreshRate);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
+                peakRefreshRate);
     }
 
     @Test
     public void testMinRefreshRate_FlagEnabled() {
         when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
                 .thenReturn(true);
-        float highestRefreshRate1 = 130;
-        float highestRefreshRate2 = 132;
-        doReturn(highestRefreshRate1).when(() ->
-                RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID));
-        doReturn(highestRefreshRate2).when(() ->
-                RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID_2));
+        float highestRefreshRate = 130;
+        doReturn(highestRefreshRate).when(() ->
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
@@ -1650,12 +1607,9 @@
 
         setMinRefreshRate(Float.POSITIVE_INFINITY);
 
-        Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        Vote vote2 = director.getVote(DISPLAY_ID_2,
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
                 Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ highestRefreshRate1,
-                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
-        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ highestRefreshRate2,
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate,
                 /* frameRateHigh= */ Float.POSITIVE_INFINITY);
     }
 
@@ -1674,44 +1628,13 @@
 
         setMinRefreshRate(minRefreshRate);
 
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate,
                 /* frameRateHigh= */ Float.POSITIVE_INFINITY);
     }
 
     @Test
-    public void testMinRefreshRate_DisplayChanged() {
-        when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
-                .thenReturn(true);
-        float highestRefreshRate = 130;
-        doReturn(highestRefreshRate).when(() ->
-                RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID));
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        Sensor lightSensor = createLightSensor();
-        SensorManager sensorManager = createMockSensorManager(lightSensor);
-        director.start(sensorManager);
-
-        setMinRefreshRate(Float.POSITIVE_INFINITY);
-
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate,
-                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
-
-        // The highest refresh rate of the display changes
-        highestRefreshRate = 140;
-        doReturn(highestRefreshRate).when(() ->
-                RefreshRateSettingsUtils.findHighestRefreshRate(mContext, DISPLAY_ID));
-        director.getDisplayObserver().onDisplayChanged(DISPLAY_ID);
-
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate,
-                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
-    }
-
-    @Test
     public void testSensorRegistration() {
         // First, configure brightness zones or DMD won't register for sensor data.
         final FakeDeviceConfig config = mInjector.getDeviceConfig();
@@ -3406,7 +3329,7 @@
     public static class FakesInjector implements DisplayModeDirector.Injector {
         private final FakeDeviceConfig mDeviceConfig;
         private final DisplayInfo mDisplayInfo;
-        private final Map<Integer, Display> mDisplays;
+        private final Display mDisplay;
         private boolean mDisplayInfoValid = true;
         private final DisplayManagerInternal mDisplayManagerInternal;
         private final StatusBarManagerInternal mStatusBarManagerInternal;
@@ -3427,8 +3350,7 @@
             mDisplayInfo.defaultModeId = MODE_ID;
             mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
                     800, 600, /* refreshRate= */ 60)};
-            mDisplays = Map.of(DISPLAY_ID, createDisplay(DISPLAY_ID),
-                    DISPLAY_ID_2, createDisplay(DISPLAY_ID_2));
+            mDisplay = createDisplay(DISPLAY_ID);
             mDisplayManagerInternal = displayManagerInternal;
             mStatusBarManagerInternal = statusBarManagerInternal;
             mSensorManagerInternal = sensorManagerInternal;
@@ -3459,12 +3381,12 @@
 
         @Override
         public Display getDisplay(int displayId) {
-            return mDisplays.get(displayId);
+            return mDisplay;
         }
 
         @Override
         public Display[] getDisplays() {
-            return mDisplays.values().toArray(new Display[0]);
+            return new Display[] { mDisplay };
         }
 
         @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index be29163..1a3a6a3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -46,6 +46,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -61,6 +62,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.Sensor;
@@ -128,6 +130,8 @@
     @Mock
     private ConnectivityManager mConnectivityManager;
     @Mock
+    private ContentResolver mContentResolver;
+    @Mock
     private IActivityManager mIActivityManager;
     @Mock
     private LocationManager mLocationManager;
@@ -332,7 +336,7 @@
                         anyString(), any(Executor.class),
                         any(DeviceConfig.OnPropertiesChangedListener.class)));
         doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock
-                -> mock(DeviceConfig.Properties.class))
+                -> new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_DEVICE_IDLE).build())
                 .when(() -> DeviceConfig.getProperties(
                         anyString(), ArgumentMatchers.<String>any()));
         when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock);
@@ -347,6 +351,7 @@
         mAppStateTracker = new AppStateTrackerForTest(getContext(), Looper.getMainLooper());
         mAnyMotionDetector = new AnyMotionDetectorForTest();
         mInjector = new InjectorForTest(getContext());
+        doNothing().when(mContentResolver).registerContentObserver(any(), anyBoolean(), any());
 
         doReturn(mWearModeManagerInternal)
                 .when(() -> LocalServices.getService(WearModeManagerInternal.class));
@@ -366,7 +371,8 @@
         mDeviceIdleController.setLightEnabledForTest(true);
 
         // Get the same Constants object that mDeviceIdleController got.
-        mConstants = mInjector.getConstants(mDeviceIdleController);
+        mConstants = mInjector.getConstants(mDeviceIdleController,
+                mInjector.getHandler(mDeviceIdleController), mContentResolver);
 
         final ArgumentCaptor<TelephonyCallback> telephonyCallbackCaptor =
                 ArgumentCaptor.forClass(TelephonyCallback.class);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java
new file mode 100644
index 0000000..c5e6824
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.location.GnssCapabilities;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.flags.Flags;
+import android.location.provider.ProviderRequest;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+import com.android.server.timedetector.TimeDetectorInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+@Presubmit
+@androidx.test.filters.SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssLocationProviderTest {
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .setStrictness(Strictness.WARN)
+            .mockStatic(Settings.Global.class)
+            .build();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mLocationManagerInternal;
+    private @Mock LocationManager mLocationManager;
+    private @Mock TimeDetectorInternal mTimeDetectorInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock GnssMetrics mGnssMetrics;
+    private @Mock PowerManager mPowerManager;
+    private @Mock TelephonyManager mTelephonyManager;
+    private @Mock AppOpsManager mAppOpsManager;
+    private @Mock AlarmManager mAlarmManager;
+    private @Mock PowerManager.WakeLock mWakeLock;
+    private @Mock ContentResolver mContentResolver;
+    private @Mock UserManager mUserManager;
+    private @Mock UserHandle mUserHandle;
+    private Set<UserHandle> mUserHandleSet = new HashSet<>();
+
+    private GnssNative mGnssNative;
+
+    private GnssLocationProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn("mypackage").when(mContext).getPackageName();
+        doReturn("attribution").when(mContext).getAttributionTag();
+        doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
+        doReturn(mPowerManager).when(mContext).getSystemService("power");
+        doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
+        doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
+        doReturn(mAlarmManager).when(mContext).getSystemService(Context.ALARM_SERVICE);
+        doReturn(mLocationManager).when(mContext).getSystemService(LocationManager.class);
+        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
+        mUserHandleSet.add(mUserHandle);
+        doReturn(true).when(mLocationManager).isLocationEnabledForUser(eq(mUserHandle));
+        doReturn(mUserHandleSet).when(mUserManager).getVisibleUsers();
+        doReturn(mContentResolver).when(mContext).getContentResolver();
+        doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
+        LocalServices.addService(LocationManagerInternal.class, mLocationManagerInternal);
+        LocalServices.addService(TimeDetectorInternal.class, mTimeDetectorInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(
+                GnssNative.create(injector, mMockConfiguration)));
+        doReturn(true).when(mGnssNative).init();
+        GnssCapabilities gnssCapabilities = new GnssCapabilities.Builder().setHasScheduling(
+                true).build();
+        doReturn(gnssCapabilities).when(mGnssNative).getCapabilities();
+
+        mTestProvider = new GnssLocationProvider(mContext, mGnssNative, mGnssMetrics);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+        LocalServices.removeServiceForTest(TimeDetectorInternal.class);
+    }
+
+    @Test
+    public void testStartNavigating() {
+        ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
+                0).build();
+
+        mTestProvider.onSetRequest(providerRequest);
+        verify(mGnssNative).start();
+    }
+
+    @Test
+    public void testUpdateRequirements_sameRequest() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_GNSS_CALL_STOP_BEFORE_SET_POSITION_MODE);
+        ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
+                0).build();
+
+        mTestProvider.onSetRequest(providerRequest);
+        verify(mGnssNative).start();
+
+        // set the same request
+        mTestProvider.onSetRequest(providerRequest);
+        verify(mGnssNative, never()).stop();
+        verify(mGnssNative, times(1)).start();
+    }
+
+    @Test
+    public void testUpdateRequirements_differentRequest() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_GNSS_CALL_STOP_BEFORE_SET_POSITION_MODE);
+        ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
+                0).build();
+
+        mTestProvider.onSetRequest(providerRequest);
+        verify(mGnssNative).start();
+
+        // set a different request
+        providerRequest = new ProviderRequest.Builder().setIntervalMillis(2000).build();
+        mTestProvider.onSetRequest(providerRequest);
+        verify(mGnssNative).stop();
+        verify(mGnssNative, times(2)).start();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
index 931b38d..f8fe97e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -22,12 +22,14 @@
 import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.content.pm.UserInfo
 import android.os.Build
+import android.os.UserHandle
 import android.os.UserHandle.USER_SYSTEM
 import android.util.Log
 import com.android.server.testutils.any
 import com.android.server.testutils.spy
 import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertFalse
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -177,4 +179,13 @@
 
         assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_INTERNAL_ERROR)
     }
+
+    @Test
+    fun deletePackageLIFWithNonExistantPackage_isFalse() {
+        val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java),
+                                      mock(BroadcastHelper::class.java))
+        val result = dph.deletePackageLIF("a.nonexistent.package", UserHandle.of(USER_SYSTEM), true,
+                                          intArrayOf(0), 0, PackageRemovedInfo(), true)
+        assertFalse(result)
+    }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 7148b16..2e0ba00 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -82,6 +82,15 @@
                 .isEqualTo(20 * MINUTE_IN_MS);
         assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND))
                 .isEqualTo(40 * MINUTE_IN_MS);
+        assertThat(uidBatteryConsumer
+                .getTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND))
+                .isEqualTo(20 * MINUTE_IN_MS);
+        assertThat(uidBatteryConsumer
+                .getTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_BACKGROUND))
+                .isEqualTo(20 * MINUTE_IN_MS);
+        assertThat(uidBatteryConsumer
+                .getTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE))
+                .isEqualTo(20 * MINUTE_IN_MS);
         assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
                 .isWithin(PRECISION).of(2.0);
         assertThat(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 07c486c..079ea2c7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -193,14 +193,14 @@
         for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
             if (uidBatteryConsumer.getUid() == APP_UID1) {
                 assertUidBatteryConsumer(uidBatteryConsumer, 2124, null,
-                        5321, 7432, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 745,
+                        5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 745,
                         POWER_MODEL_UNDEFINED,
                         956, 1167, 1478,
                         true, 3554, 3776, 3998, 444, 3554, 15542, 3776, 17762, 3998, 19982,
                         444, 1110);
             } else if (uidBatteryConsumer.getUid() == APP_UID2) {
                 assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar",
-                        1111, 2222, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
+                        1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
                         BatteryConsumer.POWER_MODEL_POWER_PROFILE,
                         555, 666, 777,
                         true, 1777, 1888, 1999, 321, 1777, 7771, 1888, 8881, 1999, 9991,
@@ -269,7 +269,7 @@
                         .setStatsEndTimestamp(3000);
 
         addUidBatteryConsumer(builder, batteryStats, APP_UID1, "foo",
-                1000, 2000,
+                1000, 1500, 500,
                 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
                 BatteryConsumer.POWER_MODEL_POWER_PROFILE, 500, 600, 800,
                 1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
@@ -312,13 +312,13 @@
                 .setStatsEndTimestamp(5000);
 
         addUidBatteryConsumer(builder, batteryStats, APP_UID1, null,
-                4321, 5432,
+                4321, 5400, 32,
                 123, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 345, POWER_MODEL_ENERGY_CONSUMPTION,
                 456, 567, 678,
                 1777, 7771, 1888, 8881, 1999, 9991, 321, 654);
 
         addUidBatteryConsumer(builder, batteryStats, APP_UID2, "bar",
-                1111, 2222,
+                1111, 2220, 2,
                 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
                 BatteryConsumer.POWER_MODEL_POWER_PROFILE, 555, 666, 777,
                 1777, 7771, 1888, 8881, 1999, 9991, 321, 654);
@@ -338,7 +338,8 @@
 
     private void addUidBatteryConsumer(BatteryUsageStats.Builder builder,
             MockBatteryStatsImpl batteryStats, int uid, String packageWithHighestDrain,
-            int timeInStateForeground, int timeInStateBackground, double screenPower,
+            int timeInProcessStateForeground, int timeInProcessStateBackground,
+            int timeInProcessStateForegroundService, double screenPower,
             int screenPowerModel, double cpuPower, int cpuPowerModel, double customComponentPower,
             int cpuDuration, int customComponentDuration, double cpuPowerForeground,
             int cpuDurationForeground, double cpuPowerBackground, int cpuDurationBackground,
@@ -348,8 +349,10 @@
                 builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid);
         uidBuilder
                 .setPackageWithHighestDrain(packageWithHighestDrain)
-                .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, timeInStateForeground)
-                .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, timeInStateBackground)
+                .setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND, timeInProcessStateForeground)
+                .setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND, timeInProcessStateBackground)
+                .setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE,
+                        timeInProcessStateForegroundService)
                 .setConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower, screenPowerModel)
                 .setConsumedPower(
@@ -446,7 +449,7 @@
         for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
             if (uidBatteryConsumer.getUid() == APP_UID1) {
                 assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo",
-                        1000, 2000, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
+                        1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
                         BatteryConsumer.POWER_MODEL_POWER_PROFILE,
                         500, 600, 800,
                         true, 1777, 1888, 1999, 123, 1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
@@ -496,8 +499,9 @@
     }
 
     private void assertUidBatteryConsumer(UidBatteryConsumer uidBatteryConsumer,
-            double consumedPower, String packageWithHighestDrain, int timeInStateForeground,
-            int timeInStateBackground, int screenPower, int screenPowerModel, double cpuPower,
+            double consumedPower, String packageWithHighestDrain, int timeInProcessStateForeground,
+            int timeInProcessStateBackground, int timeInProcessStateForegroundService,
+            int screenPower, int screenPowerModel, double cpuPower,
             int cpuPowerModel, double customComponentPower, int cpuDuration,
             int customComponentDuration, boolean processStateDataIncluded,
             double totalPowerForeground, double totalPowerBackground, double totalPowerFgs,
@@ -509,9 +513,16 @@
         assertThat(uidBatteryConsumer.getPackageWithHighestDrain()).isEqualTo(
                 packageWithHighestDrain);
         assertThat(uidBatteryConsumer.getTimeInStateMs(
-                UidBatteryConsumer.STATE_FOREGROUND)).isEqualTo(timeInStateForeground);
+                UidBatteryConsumer.STATE_FOREGROUND)).isEqualTo(timeInProcessStateForeground);
         assertThat(uidBatteryConsumer.getTimeInStateMs(
-                UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo(timeInStateBackground);
+                UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo(
+                        timeInProcessStateBackground + timeInProcessStateForegroundService);
+        assertThat(uidBatteryConsumer.getTimeInProcessStateMs(
+                PROCESS_STATE_FOREGROUND)).isEqualTo(timeInProcessStateForeground);
+        assertThat(uidBatteryConsumer.getTimeInProcessStateMs(
+                PROCESS_STATE_BACKGROUND)).isEqualTo(timeInProcessStateBackground);
+        assertThat(uidBatteryConsumer.getTimeInProcessStateMs(
+                PROCESS_STATE_FOREGROUND_SERVICE)).isEqualTo(timeInProcessStateForegroundService);
         assertThat(uidBatteryConsumer.getConsumedPower(
                 BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPower);
         assertThat(uidBatteryConsumer.getPowerModel(
diff --git a/services/tests/servicestests/src/com/android/server/SecurityStateTest.java b/services/tests/servicestests/src/com/android/server/SecurityStateTest.java
new file mode 100644
index 0000000..fc91e47
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/SecurityStateTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.os.SecurityStateManager.KEY_KERNEL_VERSION;
+import static android.os.SecurityStateManager.KEY_SYSTEM_SPL;
+import static android.os.SecurityStateManager.KEY_VENDOR_SPL;
+
+import static com.android.server.SecurityStateManagerService.KERNEL_RELEASE_PATTERN;
+import static com.android.server.SecurityStateManagerService.VENDOR_SECURITY_PATCH_PROPERTY_KEY;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.regex.Matcher;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SecurityStateTest {
+    @Rule
+    public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+    @Mock
+    private Context mMockContext;
+
+    @Mock
+    private PackageManager mMockPackageManager;
+
+    @Mock
+    private Resources mMockResources;
+
+    private static final String DEFAULT_MODULE_METADATA_PROVIDER = "com.android.modulemetadata";
+    private static final String DEFAULT_MODULE_METADATA_PROVIDER_VERSION = "2023-12-01";
+    private static final String DEFAULT_SECURITY_STATE_PACKAGE = "com.google.android.gms";
+    private static final String DEFAULT_SECURITY_STATE_PACKAGE_VERSION = "2023-12-05";
+    private static final String[] SECURITY_STATE_PACKAGES =
+            new String[]{DEFAULT_SECURITY_STATE_PACKAGE};
+
+    @Before
+    public void setUp() throws Exception {
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockContext.getString(R.string.config_defaultModuleMetadataProvider))
+                .thenReturn(DEFAULT_MODULE_METADATA_PROVIDER);
+        when(mMockPackageManager.getPackageInfo(anyString(), anyInt()))
+                .thenReturn(new PackageInfo());
+        PackageInfo moduleMetadataPackageInfo = new PackageInfo();
+        moduleMetadataPackageInfo.versionName = DEFAULT_MODULE_METADATA_PROVIDER_VERSION;
+        when(mMockPackageManager.getPackageInfo(DEFAULT_MODULE_METADATA_PROVIDER, 0))
+                .thenReturn(moduleMetadataPackageInfo);
+        PackageInfo securityStatePackageInfo = new PackageInfo();
+        securityStatePackageInfo.versionName = DEFAULT_SECURITY_STATE_PACKAGE_VERSION;
+        when(mMockPackageManager.getPackageInfo(DEFAULT_SECURITY_STATE_PACKAGE, 0))
+                .thenReturn(securityStatePackageInfo);
+        when(mMockResources.getStringArray(R.array.config_securityStatePackages))
+                .thenReturn(SECURITY_STATE_PACKAGES);
+    }
+
+    @Test
+    public void testGetGlobalSecurityState_returnsBundle() {
+        SecurityStateManagerService securityState = new SecurityStateManagerService(mMockContext);
+
+        Bundle bundle = securityState.getGlobalSecurityState();
+
+        assertEquals(bundle.getString(KEY_SYSTEM_SPL), Build.VERSION.SECURITY_PATCH);
+        assertEquals(bundle.getString(KEY_VENDOR_SPL),
+                SystemProperties.get(VENDOR_SECURITY_PATCH_PROPERTY_KEY, ""));
+        Matcher matcher = KERNEL_RELEASE_PATTERN.matcher(VintfRuntimeInfo.getKernelVersion());
+        String kernelVersion = "";
+        if (matcher.matches()) {
+            kernelVersion = matcher.group(1);
+        }
+        assertEquals(bundle.getString(KEY_KERNEL_VERSION), kernelVersion);
+        assertEquals(bundle.getString(DEFAULT_MODULE_METADATA_PROVIDER),
+                DEFAULT_MODULE_METADATA_PROVIDER_VERSION);
+        assertEquals(bundle.getString(DEFAULT_SECURITY_STATE_PACKAGE),
+                DEFAULT_SECURITY_STATE_PACKAGE_VERSION);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/WatchdogTest.java b/services/tests/servicestests/src/com/android/server/WatchdogTest.java
new file mode 100644
index 0000000..34ac47e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/WatchdogTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2009 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.os.Handler;
+import android.os.SimpleClock;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.Watchdog.HandlerChecker;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.ZoneOffset;
+
+/** Test class for {@link Watchdog}. */
+@RunWith(AndroidJUnit4.class)
+public class WatchdogTest {
+    private static final int TIMEOUT_MS = 10;
+
+    private TestClock mClock;
+    private Handler mHandler;
+    private HandlerChecker mChecker;
+
+    @Before
+    public void setUp() {
+        mClock = new TestClock();
+        mHandler = mock(Handler.class);
+        mChecker =
+                new HandlerChecker(mHandler, "monitor thread", new Object(), mClock) {
+                    @Override
+                    public boolean isHandlerPolling() {
+                        return false;
+                    }
+                };
+    }
+
+    @Test
+    public void checkerPausedUntilResume() {
+        Watchdog.Monitor monitor = mock(Watchdog.Monitor.class);
+        mChecker.addMonitorLocked(monitor);
+
+        mChecker.pauseLocked("pausing");
+        mChecker.scheduleCheckLocked(TIMEOUT_MS);
+        verifyNoMoreInteractions(mHandler);
+        assertEquals(Watchdog.COMPLETED, mChecker.getCompletionStateLocked());
+
+        mChecker.resumeLocked("resuming");
+        mChecker.scheduleCheckLocked(10);
+        assertEquals(Watchdog.WAITING, mChecker.getCompletionStateLocked());
+    }
+
+    @Test
+    public void checkerPausedUntilDeadline() {
+        Watchdog.Monitor monitor = mock(Watchdog.Monitor.class);
+        mChecker.addMonitorLocked(monitor);
+
+        mChecker.pauseForLocked(10, "pausing");
+        mChecker.scheduleCheckLocked(TIMEOUT_MS);
+        verifyNoMoreInteractions(mHandler);
+        assertEquals(Watchdog.COMPLETED, mChecker.getCompletionStateLocked());
+
+        mClock.advanceBy(5);
+        verifyNoMoreInteractions(mHandler);
+        assertEquals(Watchdog.COMPLETED, mChecker.getCompletionStateLocked());
+
+        // Above the 10s timeout. Watchdog should not be paused anymore.
+        mClock.advanceBy(6);
+        mChecker.scheduleCheckLocked(TIMEOUT_MS);
+        assertEquals(Watchdog.WAITING, mChecker.getCompletionStateLocked());
+    }
+
+    @Test
+    public void checkerPausedDuringScheduledRun() {
+        Watchdog.Monitor monitor = mock(Watchdog.Monitor.class);
+        mChecker.addMonitorLocked(monitor);
+
+        mChecker.scheduleCheckLocked(TIMEOUT_MS);
+        mClock.advanceBy(5);
+        mChecker.pauseForLocked(10, "pausing");
+        verifyNoMoreInteractions(mHandler);
+        assertEquals(Watchdog.COMPLETED, mChecker.getCompletionStateLocked());
+
+        // Above the 10s timeout. Watchdog should not be paused anymore.
+        mClock.advanceBy(11);
+        mChecker.scheduleCheckLocked(TIMEOUT_MS);
+        assertEquals(Watchdog.WAITING, mChecker.getCompletionStateLocked());
+    }
+
+    @Test
+    public void blockedThread() {
+        mChecker.scheduleCheckLocked(TIMEOUT_MS);
+        assertEquals(mChecker.getCompletionStateLocked(), Watchdog.WAITING);
+
+        mClock.advanceBy(6);
+        assertEquals(Watchdog.WAITED_UNTIL_PRE_WATCHDOG, mChecker.getCompletionStateLocked());
+
+        // Above the 10s timeout.
+        mClock.advanceBy(6);
+        assertEquals(Watchdog.OVERDUE, mChecker.getCompletionStateLocked());
+    }
+
+    @Test
+    public void checkNothingBlocked() {
+        Watchdog.Monitor monitor = mock(Watchdog.Monitor.class);
+        mChecker.addMonitorLocked(monitor);
+
+        mChecker.scheduleCheckLocked(TIMEOUT_MS);
+        // scheduleCheckLocked calls #postAtFrontOfQueue which will call mChecker.run().
+        mChecker.run();
+        assertEquals(Watchdog.COMPLETED, mChecker.getCompletionStateLocked());
+        verify(monitor).monitor();
+    }
+
+    private static class TestClock extends SimpleClock {
+        long mNowMillis = 1;
+
+        TestClock() {
+            super(ZoneOffset.UTC);
+        }
+
+        @Override
+        public long millis() {
+            return mNowMillis;
+        }
+
+        public void advanceBy(long millis) {
+            mNowMillis += millis;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 82efdd3..800350a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -85,9 +85,9 @@
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
 import com.android.server.accessibility.magnification.FullScreenMagnificationController;
+import com.android.server.accessibility.magnification.MagnificationConnectionManager;
 import com.android.server.accessibility.magnification.MagnificationController;
 import com.android.server.accessibility.magnification.MagnificationProcessor;
-import com.android.server.accessibility.magnification.WindowMagnificationManager;
 import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -156,7 +156,7 @@
     @Mock private UserManagerInternal mMockUserManagerInternal;
     @Mock private IBinder mMockBinder;
     @Mock private IAccessibilityServiceClient mMockServiceClient;
-    @Mock private WindowMagnificationManager mMockWindowMagnificationMgr;
+    @Mock private MagnificationConnectionManager mMockMagnificationConnectionManager;
     @Mock private MagnificationController mMockMagnificationController;
     @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController;
     @Mock private ProxyManager mProxyManager;
@@ -180,8 +180,8 @@
                 UserManagerInternal.class, mMockUserManagerInternal);
         mInputFilter = Mockito.mock(FakeInputFilter.class);
 
-        when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn(
-                mMockWindowMagnificationMgr);
+        when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn(
+                mMockMagnificationConnectionManager);
         when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
                 mMockFullScreenMagnificationController);
         when(mMockMagnificationController.supportWindowMagnification()).thenReturn(true);
@@ -530,7 +530,7 @@
         // Invokes client change to trigger onUserStateChanged.
         mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
 
-        verify(mMockWindowMagnificationMgr).requestConnection(true);
+        verify(mMockMagnificationConnectionManager).requestConnection(true);
     }
 
     @SmallTest
@@ -547,7 +547,7 @@
         // Invokes client change to trigger onUserStateChanged.
         mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
 
-        verify(mMockWindowMagnificationMgr).requestConnection(true);
+        verify(mMockMagnificationConnectionManager).requestConnection(true);
     }
 
     @SmallTest
@@ -565,7 +565,7 @@
         // Invokes client change to trigger onUserStateChanged.
         mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
 
-        verify(mMockWindowMagnificationMgr).requestConnection(false);
+        verify(mMockMagnificationConnectionManager).requestConnection(false);
     }
 
     @SmallTest
@@ -583,7 +583,7 @@
         // Invokes client change to trigger onUserStateChanged.
         mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
 
-        verify(mMockWindowMagnificationMgr).requestConnection(true);
+        verify(mMockMagnificationConnectionManager).requestConnection(true);
     }
 
     @SmallTest
@@ -602,7 +602,7 @@
         // Invokes client change to trigger onUserStateChanged.
         mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
 
-        verify(mMockWindowMagnificationMgr).requestConnection(false);
+        verify(mMockMagnificationConnectionManager).requestConnection(false);
     }
 
     @SmallTest
@@ -616,7 +616,7 @@
         // Invokes client change to trigger onUserStateChanged.
         mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
 
-        verify(mMockWindowMagnificationMgr).requestConnection(true);
+        verify(mMockMagnificationConnectionManager).requestConnection(true);
     }
 
     @SmallTest
@@ -630,7 +630,8 @@
         // Invokes client change to trigger onUserStateChanged.
         mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
 
-        verify(mMockWindowMagnificationMgr, atLeastOnce()).removeMagnificationButton(anyInt());
+        verify(mMockMagnificationConnectionManager, atLeastOnce())
+                .removeMagnificationButton(anyInt());
     }
 
     @SmallTest
@@ -644,7 +645,7 @@
         // Invokes client change to trigger onUserStateChanged.
         mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
 
-        verify(mMockWindowMagnificationMgr, never()).removeMagnificationButton(anyInt());
+        verify(mMockMagnificationConnectionManager, never()).removeMagnificationButton(anyInt());
     }
 
     @SmallTest
@@ -659,7 +660,8 @@
         // Invokes client change to trigger onUserStateChanged.
         mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
 
-        verify(mMockWindowMagnificationMgr, atLeastOnce()).removeMagnificationButton(anyInt());
+        verify(mMockMagnificationConnectionManager, atLeastOnce())
+                .removeMagnificationButton(anyInt());
     }
 
     @SmallTest
@@ -674,7 +676,7 @@
         // Invokes client change to trigger onUserStateChanged.
         mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
 
-        verify(mMockWindowMagnificationMgr, never()).removeMagnificationButton(anyInt());
+        verify(mMockMagnificationConnectionManager, never()).removeMagnificationButton(anyInt());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index a02807f..7829fcc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -39,9 +39,9 @@
 import android.graphics.Region;
 
 import com.android.server.accessibility.magnification.FullScreenMagnificationController;
+import com.android.server.accessibility.magnification.MagnificationConnectionManager;
 import com.android.server.accessibility.magnification.MagnificationController;
 import com.android.server.accessibility.magnification.MagnificationProcessor;
-import com.android.server.accessibility.magnification.WindowMagnificationManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -66,21 +66,21 @@
     @Mock
     private FullScreenMagnificationController mMockFullScreenMagnificationController;
     @Mock
-    private WindowMagnificationManager mMockWindowMagnificationManager;
+    private MagnificationConnectionManager mMockMagnificationConnectionManager;
     FullScreenMagnificationControllerStub mFullScreenMagnificationControllerStub;
-    WindowMagnificationManagerStub mWindowMagnificationManagerStub;
+    MagnificationManagerStub mMagnificationManagerStub;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mFullScreenMagnificationControllerStub = new FullScreenMagnificationControllerStub(
                 mMockFullScreenMagnificationController);
-        mWindowMagnificationManagerStub = new WindowMagnificationManagerStub(
-                mMockWindowMagnificationManager);
+        mMagnificationManagerStub = new MagnificationManagerStub(
+                mMockMagnificationConnectionManager);
         when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
                 mMockFullScreenMagnificationController);
-        when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn(
-                mMockWindowMagnificationManager);
+        when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn(
+                mMockMagnificationConnectionManager);
         mMagnificationProcessor = new MagnificationProcessor(mMockMagnificationController);
     }
 
@@ -194,7 +194,7 @@
         doAnswer((invocation) -> {
             ((Region) invocation.getArguments()[1]).set(region);
             return null;
-        }).when(mMockWindowMagnificationManager).getMagnificationSourceBounds(eq(TEST_DISPLAY),
+        }).when(mMockMagnificationConnectionManager).getMagnificationSourceBounds(eq(TEST_DISPLAY),
                 any());
 
         final Region result = new Region();
@@ -286,7 +286,7 @@
 
         mMagnificationProcessor.resetCurrentMagnification(TEST_DISPLAY, /* animate= */false);
 
-        verify(mMockWindowMagnificationManager).disableWindowMagnification(TEST_DISPLAY, false,
+        verify(mMockMagnificationConnectionManager).disableWindowMagnification(TEST_DISPLAY, false,
                 null);
     }
 
@@ -296,7 +296,7 @@
         mMagnificationProcessor.resetAllIfNeeded(connectionId);
 
         verify(mMockFullScreenMagnificationController).resetAllIfNeeded(eq(connectionId));
-        verify(mMockWindowMagnificationManager).resetAllIfNeeded(eq(connectionId));
+        verify(mMockMagnificationConnectionManager).resetAllIfNeeded(eq(connectionId));
     }
 
     @Test
@@ -450,7 +450,7 @@
                 .setActivated(false).build();
         mMagnificationProcessor.setMagnificationConfig(TEST_DISPLAY, config, false, SERVICE_ID);
 
-        verify(mMockWindowMagnificationManager)
+        verify(mMockMagnificationConnectionManager)
                 .disableWindowMagnification(eq(TEST_DISPLAY), anyBoolean());
     }
 
@@ -481,11 +481,11 @@
             mFullScreenMagnificationControllerStub.resetAndStubMethods();
             mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(),
                     config.getCenterX(), config.getCenterY(), false, SERVICE_ID);
-            mWindowMagnificationManagerStub.deactivateIfNeed();
+            mMagnificationManagerStub.deactivateIfNeed();
         } else if (config.getMode() == MAGNIFICATION_MODE_WINDOW) {
-            mWindowMagnificationManagerStub.resetAndStubMethods();
-            mMockWindowMagnificationManager.enableWindowMagnification(displayId, config.getScale(),
-                    config.getCenterX(), config.getCenterY());
+            mMagnificationManagerStub.resetAndStubMethods();
+            mMockMagnificationConnectionManager.enableWindowMagnification(
+                    displayId, config.getScale(), config.getCenterX(), config.getCenterY());
             mFullScreenMagnificationControllerStub.deactivateIfNeed();
         }
     }
@@ -568,26 +568,26 @@
         }
     }
 
-    private static class WindowMagnificationManagerStub {
-        private final WindowMagnificationManager mWindowMagnificationManager;
+    private static class MagnificationManagerStub {
+        private final MagnificationConnectionManager mMagnificationConnectionManager;
         private float mScale = 1.0f;
         private float mCenterX = 0;
         private float mCenterY = 0;
         private boolean mIsEnabled = false;
 
-        WindowMagnificationManagerStub(
-                WindowMagnificationManager windowMagnificationManager) {
-            mWindowMagnificationManager = windowMagnificationManager;
+        MagnificationManagerStub(
+                MagnificationConnectionManager magnificationConnectionManager) {
+            mMagnificationConnectionManager = magnificationConnectionManager;
         }
 
         private void stubMethods() {
-            doAnswer(invocation -> mScale).when(mWindowMagnificationManager).getScale(
+            doAnswer(invocation -> mScale).when(mMagnificationConnectionManager).getScale(
                     TEST_DISPLAY);
-            doAnswer(invocation -> mCenterX).when(mWindowMagnificationManager).getCenterX(
+            doAnswer(invocation -> mCenterX).when(mMagnificationConnectionManager).getCenterX(
                     TEST_DISPLAY);
-            doAnswer(invocation -> mCenterY).when(mWindowMagnificationManager).getCenterY(
+            doAnswer(invocation -> mCenterY).when(mMagnificationConnectionManager).getCenterY(
                     TEST_DISPLAY);
-            doAnswer(invocation -> mIsEnabled).when(mWindowMagnificationManager)
+            doAnswer(invocation -> mIsEnabled).when(mMagnificationConnectionManager)
                     .isWindowMagnifierEnabled(TEST_DISPLAY);
 
             Answer enableWindowMagnificationStubAnswer = invocation -> {
@@ -598,10 +598,10 @@
                 return true;
             };
             doAnswer(enableWindowMagnificationStubAnswer).when(
-                    mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY),
+                    mMagnificationConnectionManager).enableWindowMagnification(eq(TEST_DISPLAY),
                     anyFloat(), anyFloat(), anyFloat());
             doAnswer(enableWindowMagnificationStubAnswer).when(
-                    mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY),
+                    mMagnificationConnectionManager).enableWindowMagnification(eq(TEST_DISPLAY),
                     anyFloat(), anyFloat(), anyFloat(), any(), anyInt());
 
             Answer disableWindowMagnificationStubAnswer = invocation -> {
@@ -609,15 +609,15 @@
                 return true;
             };
             doAnswer(disableWindowMagnificationStubAnswer).when(
-                    mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY),
+                    mMagnificationConnectionManager).disableWindowMagnification(eq(TEST_DISPLAY),
                     anyBoolean());
             doAnswer(disableWindowMagnificationStubAnswer).when(
-                    mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY),
+                    mMagnificationConnectionManager).disableWindowMagnification(eq(TEST_DISPLAY),
                     anyBoolean(), any());
         }
 
         public void resetAndStubMethods() {
-            Mockito.reset(mWindowMagnificationManager);
+            Mockito.reset(mMagnificationConnectionManager);
             stubMethods();
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index fd2cf6d..3b39160 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -540,18 +540,23 @@
         twoFingerTap();
 
         assertIn(STATE_ACTIVATED);
+        verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
+        verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(true);
     }
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
     public void testTwoFingerTripleTap_StateIsActivated_shouldInIdle() {
         goFromStateIdleTo(STATE_ACTIVATED);
+        reset(mMockMagnificationLogger);
 
         twoFingerTap();
         twoFingerTap();
         twoFingerTap();
 
         assertIn(STATE_IDLE);
+        verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
+        verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(false);
     }
 
     @Test
@@ -564,6 +569,8 @@
         twoFingerTapAndHold();
 
         assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
+        verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
+        verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(true);
     }
 
     @Test
@@ -576,6 +583,8 @@
         twoFingerSwipeAndHold();
 
         assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
+        verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean());
+        verify(mMockMagnificationLogger).logMagnificationTwoFingerTripleTap(true);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
new file mode 100644
index 0000000..3843e25
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
@@ -0,0 +1,854 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY;
+import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY_2;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import static java.lang.Float.NaN;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.accessibility.IRemoteMagnificationAnimationCallback;
+import android.view.accessibility.IWindowMagnificationConnectionCallback;
+import android.view.accessibility.MagnificationAnimationCallback;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.FlakyTest;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+
+/**
+ * Tests for WindowMagnificationManager.
+ */
+public class MagnificationConnectionManagerTest {
+
+    private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
+    private static final int SERVICE_ID = 1;
+
+    private MockWindowMagnificationConnection mMockConnection;
+    @Mock
+    private Context mContext;
+    @Mock
+    private AccessibilityTraceManager mMockTrace;
+    @Mock
+    private StatusBarManagerInternal mMockStatusBarManagerInternal;
+    @Mock
+    private MagnificationAnimationCallback mAnimationCallback;
+    @Mock
+    private MagnificationConnectionManager.Callback mMockCallback;
+    private MockContentResolver mResolver;
+    private MagnificationConnectionManager mMagnificationConnectionManager;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal);
+        mResolver = new MockContentResolver();
+        mMockConnection = new MockWindowMagnificationConnection();
+        mMagnificationConnectionManager = new MagnificationConnectionManager(mContext, new Object(),
+                mMockCallback, mMockTrace, new MagnificationScaleProvider(mContext));
+
+        when(mContext.getContentResolver()).thenReturn(mResolver);
+        stubSetConnection(false);
+
+        mResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        Settings.Secure.putFloatForUser(mResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.5f,
+                CURRENT_USER_ID);
+    }
+
+    private void stubSetConnection(boolean needDelay) {
+        doAnswer((InvocationOnMock invocation) -> {
+            final boolean connect = (Boolean) invocation.getArguments()[0];
+            // Simulates setConnection() called by another process.
+            if (needDelay) {
+                final Context context = ApplicationProvider.getApplicationContext();
+                context.getMainThreadHandler().postDelayed(
+                        () -> {
+                            mMagnificationConnectionManager.setConnection(
+                                    connect ? mMockConnection.getConnection() : null);
+                        }, 10);
+            } else {
+                mMagnificationConnectionManager.setConnection(
+                        connect ? mMockConnection.getConnection() : null);
+            }
+            return true;
+        }).when(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(anyBoolean());
+    }
+
+    @Test
+    public void setConnection_connectionIsNull_wrapperIsNullAndLinkToDeath() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        assertTrue(mMagnificationConnectionManager.isConnected());
+        verify(mMockConnection.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
+    }
+
+    @Test
+    public void setConnection_connectionIsNull_setMirrorWindowCallbackAndHasWrapper()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+
+        assertTrue(mMagnificationConnectionManager.isConnected());
+        verify(mMockConnection.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
+        verify(mMockConnection.getConnection()).setConnectionCallback(
+                any(IWindowMagnificationConnectionCallback.class));
+    }
+
+    @Test
+    public void binderDied_hasConnection_wrapperIsNullAndUnlinkToDeath() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+
+        mMockConnection.getDeathRecipient().binderDied();
+
+        assertFalse(mMagnificationConnectionManager.isConnected());
+        verify(mMockConnection.asBinder()).unlinkToDeath(mMockConnection.getDeathRecipient(),
+                0);
+    }
+
+    /**
+     * This test simulates {@link MagnificationConnectionManager#setConnection} is called by thread
+     * A and then the former connection is called by thread B. In this situation we should keep the
+     * new connection.
+     */
+    @Test
+    public void setSecondConnectionAndFormerConnectionBinderDead_hasWrapperAndNotCallUnlinkToDeath()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        MockWindowMagnificationConnection secondConnection =
+                new MockWindowMagnificationConnection();
+
+        mMagnificationConnectionManager.setConnection(secondConnection.getConnection());
+        mMockConnection.getDeathRecipient().binderDied();
+
+        assertTrue(mMagnificationConnectionManager.isConnected());
+        verify(mMockConnection.asBinder()).unlinkToDeath(mMockConnection.getDeathRecipient(), 0);
+        verify(secondConnection.asBinder(), never()).unlinkToDeath(
+                secondConnection.getDeathRecipient(), 0);
+    }
+
+    @Test
+    public void setNullConnection_hasConnection_wrapperIsNull() throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+
+        mMagnificationConnectionManager.setConnection(null);
+
+        assertFalse(mMagnificationConnectionManager.isConnected());
+        verify(mMockConnection.getConnection()).setConnectionCallback(null);
+    }
+
+    @Test
+    public void enableWithAnimation_hasConnection_enableWindowMagnification()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f);
+
+        verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(2f),
+                eq(200f), eq(300f), eq(0f), eq(0f), notNull());
+    }
+
+    @Test
+    public void enableWithCallback_hasConnection_enableWindowMagnification()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f,
+                mAnimationCallback, SERVICE_ID);
+
+        verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(2f),
+                eq(200f), eq(300f), eq(0f), eq(0f),
+                any(IRemoteMagnificationAnimationCallback.class));
+        verify(mAnimationCallback).onResult(true);
+    }
+
+    @Test
+    public void disable_hasConnectionAndEnabled_disableWindowMagnification()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
+
+        mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false);
+
+        verify(mMockConnection.getConnection()).disableWindowMagnification(eq(TEST_DISPLAY),
+                notNull());
+    }
+
+    @Test
+    public void disableWithCallback_hasConnectionAndEnabled_disableWindowMagnification()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
+
+        mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false,
+                mAnimationCallback);
+
+        verify(mMockConnection.getConnection()).disableWindowMagnification(eq(TEST_DISPLAY),
+                any(IRemoteMagnificationAnimationCallback.class));
+        verify(mAnimationCallback).onResult(true);
+    }
+
+    @Test
+    public void isWindowMagnifierEnabled_hasConnectionAndEnabled_returnExpectedValue() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN);
+
+        assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+    }
+
+    @Test
+    public void getPersistedScale() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+
+        assertEquals(mMagnificationConnectionManager.getPersistedScale(TEST_DISPLAY), 2.5f);
+    }
+
+    @Test
+    public void persistScale_setValue_expectedValueInProvider() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, NaN, NaN);
+        mMagnificationConnectionManager.setScale(TEST_DISPLAY, 2.5f);
+
+        mMagnificationConnectionManager.persistScale(TEST_DISPLAY);
+
+        assertEquals(Settings.Secure.getFloatForUser(mResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0f,
+                CURRENT_USER_ID), 2.5f);
+    }
+
+    @Test
+    public void persistScale_setValueWhenScaleIsOne_nothingChanged() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        final float persistedScale =
+                mMagnificationConnectionManager.getPersistedScale(TEST_DISPLAY);
+
+        mMagnificationConnectionManager.setScale(TEST_DISPLAY, 1.0f);
+        mMagnificationConnectionManager.persistScale(TEST_DISPLAY);
+
+        assertEquals(Settings.Secure.getFloatForUser(mResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0f,
+                CURRENT_USER_ID), persistedScale);
+    }
+
+    @Test
+    public void scaleSetterGetter_enabledOnTestDisplay_expectedValue() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, NaN, NaN);
+
+        mMagnificationConnectionManager.setScale(TEST_DISPLAY, 2.5f);
+
+        assertEquals(mMagnificationConnectionManager.getScale(TEST_DISPLAY), 2.5f);
+    }
+
+    @Test
+    public void scaleSetterGetter_scaleIsOutOfRang_getNormalizeValue() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN);
+
+        mMagnificationConnectionManager.setScale(TEST_DISPLAY, 10.0f);
+
+        assertEquals(mMagnificationConnectionManager.getScale(TEST_DISPLAY),
+                MagnificationScaleProvider.MAX_SCALE);
+    }
+
+    @FlakyTest(bugId = 297879435)
+    @Test
+    public void logTrackingTypingFocus_processScroll_logDuration() {
+        MagnificationConnectionManager spyMagnificationConnectionManager = spy(
+                mMagnificationConnectionManager);
+        spyMagnificationConnectionManager.enableWindowMagnification(
+                TEST_DISPLAY, 3.0f, 50f, 50f);
+        spyMagnificationConnectionManager.onImeWindowVisibilityChanged(
+                TEST_DISPLAY, /* shown */ true);
+
+        spyMagnificationConnectionManager.processScroll(TEST_DISPLAY, 10f, 10f);
+
+        verify(spyMagnificationConnectionManager).logTrackingTypingFocus(anyLong());
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnifier()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        final Region outRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+        final Rect requestedRect = outRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+        mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY);
+
+        mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        verify(mMockConnection.getConnection(), never())
+                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
+    }
+
+
+    @Test
+    public void onRectangleOnScreenRequested_trackingDisabledByScroll_withoutMovingMagnifier()
+            throws RemoteException {
+        final float distanceX = 10f;
+        final float distanceY = 10f;
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        final Region outRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+        final Rect requestedRect = outRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+        mMagnificationConnectionManager.processScroll(TEST_DISPLAY, distanceX, distanceY);
+
+        mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        verify(mMockConnection.getConnection(), never())
+                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnifier()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        final Region outRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+        final Rect requestedRect = outRegion.getBounds();
+        requestedRect.inset(-10, -10);
+
+        mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        verify(mMockConnection.getConnection(), never())
+                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
+    }
+    @Test
+    public void onRectangleOnScreenRequested_imeVisibilityDefaultInvisible_withoutMovingMagnifier()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        final Region outRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+        final Rect requestedRect = outRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+
+        mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        verify(mMockConnection.getConnection(), never())
+                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnifier()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        final Region outRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+        final Rect requestedRect = outRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+
+        mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+                eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
+                any(IRemoteMagnificationAnimationCallback.class));
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_imeInvisible_withoutMovingMagnifier()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        final Region outRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+        final Rect requestedRect = outRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+        mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, false);
+
+        mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        verify(mMockConnection.getConnection(), never())
+                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnifier()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY);
+        mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        final Region outRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
+        final Rect requestedRect = outRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+
+        mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+                eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
+                any(IRemoteMagnificationAnimationCallback.class));
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnifier() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        final Region beforeRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
+        final Rect requestedRect = beforeRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+        mMagnificationConnectionManager.setMagnificationFollowTypingEnabled(false);
+
+        mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        final Region afterRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion);
+        assertEquals(afterRegion, beforeRegion);
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_trackingDisabled_withoutMovingMagnifier() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        mMagnificationConnectionManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false);
+        final Region beforeRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
+        final Rect requestedRect = beforeRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+
+        mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        final Region afterRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion);
+        assertEquals(afterRegion, beforeRegion);
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_trackingDisabledAndEnabledMagnifier_movingMagnifier()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
+        mMagnificationConnectionManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        mMagnificationConnectionManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false);
+        final Region beforeRegion = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
+        final Rect requestedRect = beforeRegion.getBounds();
+        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
+        mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false);
+        // Enabling a window magnifier again will turn on the tracking typing focus functionality.
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, NaN, NaN, NaN);
+
+        mMagnificationConnectionManager.onRectangleOnScreenRequested(TEST_DISPLAY,
+                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
+
+        verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
+                eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
+                any(IRemoteMagnificationAnimationCallback.class));
+    }
+
+    @Test
+    public void moveWindowMagnifier_enabled_invokeConnectionMethod() throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN);
+
+        mMagnificationConnectionManager.moveWindowMagnification(TEST_DISPLAY, 200, 300);
+        verify(mMockConnection.getConnection()).moveWindowMagnifier(TEST_DISPLAY, 200, 300);
+    }
+
+    @Test
+    public void showMagnificationButton_hasConnection_invokeConnectionMethod()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+
+        mMagnificationConnectionManager.showMagnificationButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        verify(mMockConnection.getConnection()).showMagnificationButton(TEST_DISPLAY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+
+        mMagnificationConnectionManager.removeMagnificationButton(TEST_DISPLAY);
+        verify(mMockConnection.getConnection()).removeMagnificationButton(TEST_DISPLAY);
+    }
+
+    @Test
+    public void removeMagnificationSettingsPanel_hasConnection_invokeConnectionMethod()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+
+        mMagnificationConnectionManager.removeMagnificationSettingsPanel(TEST_DISPLAY);
+        verify(mMockConnection.getConnection()).removeMagnificationSettingsPanel(TEST_DISPLAY);
+    }
+
+    @Test
+    public void onUserMagnificationScaleChanged_hasConnection_invokeConnectionMethod()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+
+        final float testScale = 3f;
+        mMagnificationConnectionManager.onUserMagnificationScaleChanged(
+                CURRENT_USER_ID, TEST_DISPLAY, testScale);
+        verify(mMockConnection.getConnection()).onUserMagnificationScaleChanged(
+                eq(CURRENT_USER_ID), eq(TEST_DISPLAY), eq(testScale));
+    }
+
+    @Test
+    public void pointersInWindow_magnifierEnabled_returnCorrectValue() throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
+        mMockConnection.getConnectionCallback().onWindowMagnifierBoundsChanged(TEST_DISPLAY,
+                new Rect(0, 0, 500, 500));
+        PointF[] pointersLocation = new PointF[2];
+        pointersLocation[0] = new PointF(600, 700);
+        pointersLocation[1] = new PointF(300, 400);
+        MotionEvent event = generatePointersDownEvent(pointersLocation);
+
+        assertEquals(mMagnificationConnectionManager.pointersInWindow(TEST_DISPLAY, event), 1);
+    }
+
+    @Test
+    public void onPerformScaleAction_magnifierEnabled_notifyAction() throws RemoteException {
+        final float newScale = 4.0f;
+        final boolean updatePersistence = true;
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
+
+        mMockConnection.getConnectionCallback().onPerformScaleAction(
+                TEST_DISPLAY, newScale, updatePersistence);
+
+        verify(mMockCallback).onPerformScaleAction(
+                eq(TEST_DISPLAY), eq(newScale), eq(updatePersistence));
+    }
+
+    @Test
+    public void onAccessibilityActionPerformed_magnifierEnabled_notifyAction()
+            throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
+
+        mMockConnection.getConnectionCallback().onAccessibilityActionPerformed(TEST_DISPLAY);
+
+        verify(mMockCallback).onAccessibilityActionPerformed(eq(TEST_DISPLAY));
+    }
+
+    @Test
+    public void binderDied_windowMagnifierIsEnabled_resetState() throws RemoteException {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
+
+        mMockConnection.getDeathRecipient().binderDied();
+
+        assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+    }
+
+    @Test
+    public void
+            requestConnectionToNull_disableAllMagnifiersAndRequestWindowMagnificationConnection()
+            throws RemoteException {
+        assertTrue(mMagnificationConnectionManager.requestConnection(true));
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
+
+        assertTrue(mMagnificationConnectionManager.requestConnection(false));
+
+        verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY, null);
+        verify(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(false);
+    }
+
+    @Test
+    public void requestConnection_requestWindowMagnificationConnection() throws RemoteException {
+        assertTrue(mMagnificationConnectionManager.requestConnection(true));
+        verify(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(true);
+    }
+
+    @Test
+    public void isConnected_requestConnection_expectedValue() throws RemoteException {
+        mMagnificationConnectionManager.requestConnection(true);
+        assertTrue(mMagnificationConnectionManager.isConnected());
+
+        mMagnificationConnectionManager.requestConnection(false);
+        assertFalse(mMagnificationConnectionManager.isConnected());
+    }
+
+    @Test
+    public void requestConnection_registerAndUnregisterBroadcastReceiver() {
+        assertTrue(mMagnificationConnectionManager.requestConnection(true));
+        verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
+
+        assertTrue(mMagnificationConnectionManager.requestConnection(false));
+        verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
+    }
+
+    @Test
+    public void requestConnectionToNull_expectedGetterResults() {
+        mMagnificationConnectionManager.requestConnection(true);
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1);
+
+        mMagnificationConnectionManager.requestConnection(false);
+
+        assertEquals(1f, mMagnificationConnectionManager.getScale(TEST_DISPLAY), 0);
+        assertTrue(Float.isNaN(mMagnificationConnectionManager.getCenterX(TEST_DISPLAY)));
+        assertTrue(Float.isNaN(mMagnificationConnectionManager.getCenterY(TEST_DISPLAY)));
+        final Region bounds = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, bounds);
+        assertTrue(bounds.isEmpty());
+    }
+
+    @Test
+    public void enableWindowMagnification_connecting_invokeConnectionMethodAfterConnected()
+            throws RemoteException {
+        stubSetConnection(true);
+        mMagnificationConnectionManager.requestConnection(true);
+
+        assertTrue(mMagnificationConnectionManager.enableWindowMagnification(
+                TEST_DISPLAY, 3f, 1, 1));
+
+        // Invoke enableWindowMagnification if the connection is connected.
+        verify(mMockConnection.getConnection()).enableWindowMagnification(
+                eq(TEST_DISPLAY), eq(3f),
+                eq(1f), eq(1f), eq(0f), eq(0f), notNull());
+    }
+
+    @Test
+    public void resetAllMagnification_enabledBySameId_windowMagnifiersDisabled() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f,
+                100f, 200f, null,
+                MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID);
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY_2, 3f,
+                100f, 200f, null,
+                MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID);
+        assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+        assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY_2));
+
+        mMagnificationConnectionManager.resetAllIfNeeded(SERVICE_ID);
+
+        assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+        assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY_2));
+    }
+
+    @Test
+    public void resetAllMagnification_enabledByDifferentId_windowMagnifierDisabled() {
+        final int serviceId2 = SERVICE_ID + 1;
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f,
+                100f, 200f, null,
+                MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID);
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY_2, 3f,
+                100f, 200f, null,
+                MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER, serviceId2);
+        assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+        assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY_2));
+
+        mMagnificationConnectionManager.resetAllIfNeeded(SERVICE_ID);
+
+        assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+        assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY_2));
+    }
+
+    @Test
+    public void onScreenOff_windowMagnifierIsEnabled_removeButtonAndDisableWindowMagnification()
+            throws RemoteException {
+        mMagnificationConnectionManager.requestConnection(true);
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN);
+
+        mMagnificationConnectionManager.mScreenStateReceiver.onReceive(mContext,
+                new Intent(Intent.ACTION_SCREEN_OFF));
+
+        verify(mMockConnection.getConnection()).removeMagnificationButton(TEST_DISPLAY);
+        verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY, null);
+        assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+    }
+
+    @Test
+    public void centerGetter_enabledOnTestDisplay_expectedValues() {
+        mMagnificationConnectionManager.requestConnection(true);
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f);
+
+        assertEquals(mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 100f);
+        assertEquals(mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 200f);
+    }
+
+    @Test
+    public void centerGetter_enabledOnTestDisplayWindowAtCenter_expectedValues()
+            throws RemoteException {
+        mMagnificationConnectionManager.requestConnection(true);
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f,
+                100f, 200f, MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER);
+
+        assertEquals(mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 100f);
+        assertEquals(mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 200f);
+
+        verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f),
+                eq(100f), eq(200f), eq(0f), eq(0f), notNull());
+    }
+
+    @Test
+    public void centerGetter_enabledOnTestDisplayWindowAtLeftTop_expectedValues()
+            throws RemoteException {
+        mMagnificationConnectionManager.requestConnection(true);
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f,
+                100f, 200f, MagnificationConnectionManager.WINDOW_POSITION_AT_TOP_LEFT);
+
+        assertEquals(mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 100f);
+        assertEquals(mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 200f);
+
+        verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f),
+                eq(100f), eq(200f), eq(-1f), eq(-1f), notNull());
+    }
+
+    @Test
+    public void magnifierGetters_disabled_expectedValues() {
+        mMagnificationConnectionManager.requestConnection(true);
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f,
+                100f, 200f, MagnificationConnectionManager.WINDOW_POSITION_AT_CENTER);
+
+        mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false);
+
+        assertEquals(1f, mMagnificationConnectionManager.getScale(TEST_DISPLAY), 0);
+        assertTrue(Float.isNaN(mMagnificationConnectionManager.getCenterX(TEST_DISPLAY)));
+        assertTrue(Float.isNaN(mMagnificationConnectionManager.getCenterY(TEST_DISPLAY)));
+        final Region bounds = new Region();
+        mMagnificationConnectionManager.getMagnificationSourceBounds(TEST_DISPLAY, bounds);
+        assertTrue(bounds.isEmpty());
+    }
+
+    @Test
+    public void onDisplayRemoved_enabledOnTestDisplay_disabled() {
+        mMagnificationConnectionManager.requestConnection(true);
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f);
+
+        mMagnificationConnectionManager.onDisplayRemoved(TEST_DISPLAY);
+
+        assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+    }
+
+    @Test
+    public void onWindowMagnificationActivationState_magnifierEnabled_notifyActivatedState() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
+
+        verify(mMockCallback).onWindowMagnificationActivationState(TEST_DISPLAY, true);
+    }
+
+    @Test
+    public void onWindowMagnificationActivationState_magnifierDisabled_notifyDeactivatedState() {
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
+        mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false);
+
+        verify(mMockCallback).onWindowMagnificationActivationState(TEST_DISPLAY, false);
+
+        Mockito.reset(mMockCallback);
+        mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false);
+
+        verify(mMockCallback, never()).onWindowMagnificationActivationState(eq(TEST_DISPLAY),
+                anyBoolean());
+    }
+
+    private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) {
+        final int len = pointersLocation.length;
+
+        final MotionEvent.PointerProperties[] pp = new MotionEvent.PointerProperties[len];
+        for (int i = 0; i < len; i++) {
+            MotionEvent.PointerProperties pointerProperty = new MotionEvent.PointerProperties();
+            pointerProperty.id = i;
+            pointerProperty.toolType = MotionEvent.TOOL_TYPE_FINGER;
+            pp[i] = pointerProperty;
+        }
+
+        final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[len];
+        for (int i = 0; i < len; i++) {
+            MotionEvent.PointerCoords pointerCoord = new MotionEvent.PointerCoords();
+            pointerCoord.x = pointersLocation[i].x;
+            pointerCoord.y = pointersLocation[i].y;
+            pc[i] = pointerCoord;
+        }
+
+        return MotionEvent.obtain(
+                /* downTime */ SystemClock.uptimeMillis(),
+                /* eventTime */ SystemClock.uptimeMillis(),
+                /* action */ MotionEvent.ACTION_POINTER_DOWN,
+                /* pointerCount */ pc.length,
+                /* pointerProperties */ pp,
+                /* pointerCoords */ pc,
+                /* metaState */ 0,
+                /* buttonState */ 0,
+                /* xPrecision */ 1.0f,
+                /* yPrecision */ 1.0f,
+                /* deviceId */ 0,
+                /* edgeFlags */ 0,
+                /* source */ InputDevice.SOURCE_TOUCHSCREEN,
+                /* flags */ 0);
+    }
+
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
index cfd0289..8f85f11 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
@@ -39,7 +39,7 @@
 /**
  * Tests for MagnificationConnectionWrapper. We don't test {@code
  * MagnificationConnectionWrapper#linkToDeath(IBinder.DeathRecipient)} since it's tested in
- * {@link WindowMagnificationManagerTest}.
+ * {@link MagnificationConnectionManagerTest}.
  */
 public class MagnificationConnectionWrapperTest {
 
@@ -73,9 +73,9 @@
     }
 
     @Test
-    public void setScale() throws RemoteException {
-        mConnectionWrapper.setScale(TEST_DISPLAY, 3.0f);
-        verify(mConnection).setScale(TEST_DISPLAY, 3.0f);
+    public void setScaleForWindowMagnification() throws RemoteException {
+        mConnectionWrapper.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f);
+        verify(mConnection).setScaleForWindowMagnification(TEST_DISPLAY, 3.0f);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index d4c6fad..e8cdf35 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -131,7 +131,7 @@
     private ArgumentCaptor<MagnificationAnimationCallback> mCallbackArgumentCaptor;
 
     private MockWindowMagnificationConnection mMockConnection;
-    private WindowMagnificationManager mWindowMagnificationManager;
+    private MagnificationConnectionManager mMagnificationConnectionManager;
     private MockContentResolver mMockResolver;
     private MagnificationController mMagnificationController;
     private final WindowMagnificationMgrCallbackDelegate
@@ -205,13 +205,14 @@
         ));
         mScreenMagnificationController.register(TEST_DISPLAY);
 
-        mWindowMagnificationManager = spy(new WindowMagnificationManager(mContext, globalLock,
-                mWindowMagnificationCallbackDelegate, mTraceManager, mScaleProvider));
+        mMagnificationConnectionManager = spy(
+                new MagnificationConnectionManager(mContext, globalLock,
+                        mWindowMagnificationCallbackDelegate, mTraceManager, mScaleProvider));
         mMockConnection = new MockWindowMagnificationConnection(true);
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
 
         mMagnificationController = spy(new MagnificationController(mService, globalLock, mContext,
-                mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider,
+                mScreenMagnificationController, mMagnificationConnectionManager, mScaleProvider,
                 ConcurrentUtils.DIRECT_EXECUTOR));
         mMagnificationController.setMagnificationCapabilities(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
@@ -254,8 +255,10 @@
         mCallbackArgumentCaptor.getValue().onResult(true);
         mMockConnection.invokeCallbacks();
         verify(mTransitionCallBack).onResult(TEST_DISPLAY, true);
-        assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0);
-        assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0);
+        assertEquals(MAGNIFIED_CENTER_X,
+                mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 0);
+        assertEquals(MAGNIFIED_CENTER_Y,
+                mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 0);
     }
 
     @Test
@@ -297,8 +300,10 @@
 
         mMockConnection.invokeCallbacks();
         verify(mTransitionCallBack).onResult(TEST_DISPLAY, true);
-        assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0);
-        assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0);
+        assertEquals(MAGNIFIED_CENTER_X,
+                mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 0);
+        assertEquals(MAGNIFIED_CENTER_Y,
+                mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 0);
     }
 
     @Test
@@ -316,7 +321,7 @@
         // The first time is triggered when window mode is activated.
         // The second time is triggered when activating the window mode again.
         // The third time is triggered when the transition is completed.
-        verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_WINDOW));
     }
 
@@ -330,7 +335,7 @@
                 mTransitionCallBack);
         mMockConnection.invokeCallbacks();
 
-        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+        assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
         verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY,
                 DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
                 true, MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -350,7 +355,7 @@
                 mTransitionCallBack);
         mMockConnection.invokeCallbacks();
 
-        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+        assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
         verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE,
                 magnificationBounds.exactCenterX(), magnificationBounds.exactCenterY(), true,
                 MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -386,11 +391,11 @@
                 mTransitionCallBack);
 
         // Enable window magnification while animating.
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE,
+        mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE,
                 Float.NaN, Float.NaN, null, TEST_SERVICE_ID);
         mMockConnection.invokeCallbacks();
 
-        assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+        assertTrue(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
         verify(mScreenMagnificationController, never()).setScaleAndCenter(TEST_DISPLAY,
                 DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
                 true, MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -408,8 +413,10 @@
 
         verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), eq(false));
         mMockConnection.invokeCallbacks();
-        assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0);
-        assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0);
+        assertEquals(MAGNIFIED_CENTER_X,
+                mMagnificationConnectionManager.getCenterX(TEST_DISPLAY), 0);
+        assertEquals(MAGNIFIED_CENTER_Y,
+                mMagnificationConnectionManager.getCenterY(TEST_DISPLAY), 0);
     }
 
     @Test
@@ -438,7 +445,7 @@
                 animate, TEST_SERVICE_ID);
         mMockConnection.invokeCallbacks();
 
-        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+        assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
         verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
                 eq(DEFAULT_SCALE), eq(MAGNIFIED_CENTER_X), eq(MAGNIFIED_CENTER_Y),
                 any(MagnificationAnimationCallback.class), eq(TEST_SERVICE_ID));
@@ -564,7 +571,7 @@
         mMagnificationController.onDisplayRemoved(TEST_DISPLAY);
 
         verify(mScreenMagnificationController).onDisplayRemoved(TEST_DISPLAY);
-        verify(mWindowMagnificationManager).onDisplayRemoved(TEST_DISPLAY);
+        verify(mMagnificationConnectionManager).onDisplayRemoved(TEST_DISPLAY);
         verify(mScaleProvider).onDisplayRemoved(TEST_DISPLAY);
     }
 
@@ -573,7 +580,7 @@
         mMagnificationController.updateUserIdIfNeeded(SECOND_USER_ID);
 
         verify(mScreenMagnificationController).resetAllIfNeeded(false);
-        verify(mWindowMagnificationManager).disableAllWindowMagnifiers();
+        verify(mMagnificationConnectionManager).disableAllWindowMagnifiers();
         verify(mScaleProvider).onUserChanged(SECOND_USER_ID);
     }
 
@@ -584,7 +591,7 @@
         mMagnificationController.onRequestMagnificationSpec(TEST_DISPLAY, TEST_SERVICE_ID);
         mMockConnection.invokeCallbacks();
 
-        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+        assertFalse(mMagnificationConnectionManager.isWindowMagnifierEnabled(TEST_DISPLAY));
     }
 
     @Test
@@ -594,11 +601,11 @@
 
         // The first time is trigger when fullscreen mode is activated.
         // The second time is triggered when magnification spec is changed.
-        verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_FULLSCREEN));
         // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
         // in current capability and mode, and the magnification is activated.
-        verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+        verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel(
                 eq(TEST_DISPLAY));
     }
 
@@ -625,8 +632,8 @@
 
         mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
 
-        verify(mWindowMagnificationManager).setScale(eq(TEST_DISPLAY), eq(newScale));
-        verify(mWindowMagnificationManager, never()).persistScale(eq(TEST_DISPLAY));
+        verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY), eq(newScale));
+        verify(mMagnificationConnectionManager, never()).persistScale(eq(TEST_DISPLAY));
     }
 
     @Test
@@ -669,7 +676,7 @@
         assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0);
         assertEquals(config.getScale(), actualConfig.getScale(), 0);
 
-        verify(mWindowMagnificationManager).onUserMagnificationScaleChanged(
+        verify(mMagnificationConnectionManager).onUserMagnificationScaleChanged(
                 /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(config.getScale()));
     }
 
@@ -677,11 +684,11 @@
     public void onSourceBoundChanged_windowEnabled_notifyMagnificationChanged()
             throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
-        reset(mWindowMagnificationManager);
+        reset(mMagnificationConnectionManager);
 
         mMagnificationController.onSourceBoundsChanged(TEST_DISPLAY, TEST_RECT);
 
-        verify(mWindowMagnificationManager).onUserMagnificationScaleChanged(
+        verify(mMagnificationConnectionManager).onUserMagnificationScaleChanged(
                 /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(DEFAULT_SCALE));
     }
 
@@ -780,11 +787,11 @@
 
         // The first time is triggered when window mode is activated.
         // The second time is triggered when accessibility action performed.
-        verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_WINDOW));
         // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
         // in current capability and mode, and the magnification is activated.
-        verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+        verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel(
                 eq(TEST_DISPLAY));
     }
 
@@ -799,10 +806,11 @@
 
         // The first time is triggered when window mode is activated.
         // The second time is triggered when accessibility action performed.
-        verify(mWindowMagnificationManager, times(2)).removeMagnificationButton(eq(TEST_DISPLAY));
+        verify(mMagnificationConnectionManager, times(2))
+                .removeMagnificationButton(eq(TEST_DISPLAY));
         // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
         // in current capability and mode, and the magnification is activated.
-        verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+        verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel(
                 eq(TEST_DISPLAY));
     }
 
@@ -816,7 +824,7 @@
     public void deactivateWindowMagnification_windowActivated_triggerCallbackAndLogUsage()
             throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
-        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, /* clear= */ true);
+        mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, /* clear= */ true);
 
         verify(mMagnificationController).onWindowMagnificationActivationState(
                 eq(TEST_DISPLAY), eq(false));
@@ -828,7 +836,7 @@
     public void setPreferenceMagnificationFollowTypingEnabled_setPrefDisabled_disableAll() {
         mMagnificationController.setMagnificationFollowTypingEnabled(false);
 
-        verify(mWindowMagnificationManager).setMagnificationFollowTypingEnabled(eq(false));
+        verify(mMagnificationConnectionManager).setMagnificationFollowTypingEnabled(eq(false));
         verify(mScreenMagnificationController).setMagnificationFollowTypingEnabled(eq(false));
     }
 
@@ -850,7 +858,7 @@
 
         verify(mScreenMagnificationController).onRectangleOnScreenRequested(eq(TEST_DISPLAY),
                 eq(TEST_RECT.left), eq(TEST_RECT.top), eq(TEST_RECT.right), eq(TEST_RECT.bottom));
-        verify(mWindowMagnificationManager, never()).onRectangleOnScreenRequested(anyInt(),
+        verify(mMagnificationConnectionManager, never()).onRectangleOnScreenRequested(anyInt(),
                 anyInt(), anyInt(), anyInt(), anyInt());
     }
 
@@ -867,7 +875,7 @@
 
         verify(mScreenMagnificationController, never()).onRectangleOnScreenRequested(anyInt(),
                 anyInt(), anyInt(), anyInt(), anyInt());
-        verify(mWindowMagnificationManager, never()).onRectangleOnScreenRequested(anyInt(),
+        verify(mMagnificationConnectionManager, never()).onRectangleOnScreenRequested(anyInt(),
                 anyInt(), anyInt(), anyInt(), anyInt());
     }
 
@@ -880,7 +888,7 @@
 
         verify(mScreenMagnificationController, never()).onRectangleOnScreenRequested(
                 eq(TEST_DISPLAY), anyInt(), anyInt(), anyInt(), anyInt());
-        verify(mWindowMagnificationManager, never()).onRectangleOnScreenRequested(anyInt(),
+        verify(mMagnificationConnectionManager, never()).onRectangleOnScreenRequested(anyInt(),
                 anyInt(), anyInt(), anyInt(), anyInt());
     }
 
@@ -895,7 +903,7 @@
 
         verify(mScreenMagnificationController, never()).onRectangleOnScreenRequested(
                 eq(TEST_DISPLAY), anyInt(), anyInt(), anyInt(), anyInt());
-        verify(mWindowMagnificationManager, never()).onRectangleOnScreenRequested(anyInt(),
+        verify(mMagnificationConnectionManager, never()).onRectangleOnScreenRequested(anyInt(),
                 anyInt(), anyInt(), anyInt(), anyInt());
     }
 
@@ -970,7 +978,8 @@
 
         mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
 
-        verify(mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY), eq(false));
+        verify(mMagnificationConnectionManager)
+                .disableWindowMagnification(eq(TEST_DISPLAY), eq(false));
     }
 
     @Test
@@ -983,11 +992,11 @@
         // The first time is triggered when fullscreen mode is activated.
         // The second time is triggered when magnification spec is changed.
         // The third time is triggered when user interaction changed.
-        verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_FULLSCREEN));
         // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
         // in current capability and mode, and the magnification is activated.
-        verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+        verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel(
                 eq(TEST_DISPLAY));
     }
 
@@ -1001,11 +1010,11 @@
         // The first time is triggered when fullscreen mode is activated.
         // The second time is triggered when magnification spec is changed.
         // The third time is triggered when user interaction changed.
-        verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_FULLSCREEN));
         // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
         // in current capability and mode, and the magnification is activated.
-        verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+        verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel(
                 eq(TEST_DISPLAY));
     }
 
@@ -1018,11 +1027,11 @@
 
         // The first time is triggered when the window mode is activated.
         // The second time is triggered when user interaction changed.
-        verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_WINDOW));
         // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
         // in current capability and mode, and the magnification is activated.
-        verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+        verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel(
                 eq(TEST_DISPLAY));
     }
 
@@ -1035,11 +1044,11 @@
 
         // The first time is triggered when the window mode is activated.
         // The second time is triggered when user interaction changed.
-        verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_WINDOW));
         // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
         // in current capability and mode, and the magnification is activated.
-        verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+        verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel(
                 eq(TEST_DISPLAY));
     }
 
@@ -1053,11 +1062,11 @@
         mMagnificationController.onTouchInteractionStart(TEST_DISPLAY, MODE_FULLSCREEN);
         mMagnificationController.onTouchInteractionEnd(TEST_DISPLAY, MODE_FULLSCREEN);
 
-        verify(mWindowMagnificationManager, never()).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, never()).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_FULLSCREEN));
         // The first time is triggered when fullscreen mode is activated.
         // The second time is triggered when magnification spec is changed.
-        verify(mWindowMagnificationManager, times(2)).removeMagnificationSettingsPanel(
+        verify(mMagnificationConnectionManager, times(2)).removeMagnificationSettingsPanel(
                 eq(TEST_DISPLAY));
     }
 
@@ -1071,9 +1080,9 @@
         mMagnificationController.onTouchInteractionStart(TEST_DISPLAY, MODE_FULLSCREEN);
         mMagnificationController.onTouchInteractionEnd(TEST_DISPLAY, MODE_FULLSCREEN);
 
-        verify(mWindowMagnificationManager, never()).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, never()).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_FULLSCREEN));
-        verify(mWindowMagnificationManager, times(2)).removeMagnificationSettingsPanel(
+        verify(mMagnificationConnectionManager, times(2)).removeMagnificationSettingsPanel(
                 eq(TEST_DISPLAY));
     }
 
@@ -1082,11 +1091,11 @@
             throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
 
-        verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_WINDOW));
         // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
         // in current capability and mode, and the magnification is activated.
-        verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+        verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel(
                 eq(TEST_DISPLAY));
     }
 
@@ -1100,11 +1109,11 @@
         // The first time is triggered when fullscreen mode is activated.
         // The second time is triggered when magnification spec is changed.
         // The third time is triggered when fullscreen mode activation state is updated.
-        verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_FULLSCREEN));
         // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
         // in current capability and mode, and the magnification is activated.
-        verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+        verify(mMagnificationConnectionManager, never()).removeMagnificationSettingsPanel(
                 eq(TEST_DISPLAY));
     }
 
@@ -1113,10 +1122,10 @@
             throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
 
-        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
+        mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false);
 
-        verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY));
-        verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
+        verify(mMagnificationConnectionManager).removeMagnificationButton(eq(TEST_DISPLAY));
+        verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
     }
 
     @Test
@@ -1126,8 +1135,8 @@
         setMagnificationEnabled(MODE_FULLSCREEN);
         mScreenMagnificationController.reset(TEST_DISPLAY, /* animate= */ true);
 
-        verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY));
-        verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
+        verify(mMagnificationConnectionManager).removeMagnificationButton(eq(TEST_DISPLAY));
+        verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
     }
 
     @Test
@@ -1142,10 +1151,10 @@
         // The first time is triggered when fullscreen mode is activated.
         // The second time is triggered when magnification spec is changed.
         // The third time is triggered when the disable-magnification callback is triggered.
-        verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_FULLSCREEN));
         // It is triggered when the disable-magnification callback is triggered.
-        verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
+        verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
     }
 
     @Test
@@ -1163,10 +1172,10 @@
 
         // The first time is triggered when window mode is activated.
         // The second time is triggered when the disable-magnification callback is triggered.
-        verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
+        verify(mMagnificationConnectionManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
                 eq(MODE_WINDOW));
         // It is triggered when the disable-magnification callback is triggered.
-        verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
+        verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
     }
 
     @Test
@@ -1174,9 +1183,9 @@
             throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
 
-        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
+        mMagnificationConnectionManager.disableWindowMagnification(TEST_DISPLAY, false);
 
-        verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
+        verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
     }
 
     @Test
@@ -1185,7 +1194,7 @@
         setMagnificationEnabled(MODE_FULLSCREEN);
         mScreenMagnificationController.reset(TEST_DISPLAY, /* animate= */ true);
 
-        verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
+        verify(mMagnificationConnectionManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
     }
 
     @Test
@@ -1260,17 +1269,17 @@
 
     private void activateMagnifier(int displayId, int mode, float centerX, float centerY)
             throws RemoteException {
-        final boolean windowMagnifying = mWindowMagnificationManager.isWindowMagnifierEnabled(
+        final boolean windowMagnifying = mMagnificationConnectionManager.isWindowMagnifierEnabled(
                 displayId);
         if (windowMagnifying) {
-            mWindowMagnificationManager.disableWindowMagnification(displayId, false);
+            mMagnificationConnectionManager.disableWindowMagnification(displayId, false);
             mMockConnection.invokeCallbacks();
         }
         if (mode == MODE_FULLSCREEN) {
             mScreenMagnificationController.setScaleAndCenter(displayId, DEFAULT_SCALE, centerX,
                     centerY, true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
         } else {
-            mWindowMagnificationManager.enableWindowMagnification(displayId, DEFAULT_SCALE,
+            mMagnificationConnectionManager.enableWindowMagnification(displayId, DEFAULT_SCALE,
                     centerX, centerY, null, TEST_SERVICE_ID);
             mMockConnection.invokeCallbacks();
         }
@@ -1304,10 +1313,10 @@
     }
 
     private static class WindowMagnificationMgrCallbackDelegate implements
-            WindowMagnificationManager.Callback {
-        private WindowMagnificationManager.Callback mCallback;
+            MagnificationConnectionManager.Callback {
+        private MagnificationConnectionManager.Callback mCallback;
 
-        public void setDelegate(WindowMagnificationManager.Callback callback) {
+        public void setDelegate(MagnificationConnectionManager.Callback callback) {
             mCallback = callback;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index 612a091..c4be51f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -92,7 +92,7 @@
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getContext());
 
-    private WindowMagnificationManager mWindowMagnificationManager;
+    private MagnificationConnectionManager mMagnificationConnectionManager;
     private MockWindowMagnificationConnection mMockConnection;
     private SpyWindowMagnificationGestureHandler mWindowMagnificationGestureHandler;
     private WindowMagnificationGestureHandler mMockWindowMagnificationGestureHandler;
@@ -104,23 +104,23 @@
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
-        mWindowMagnificationManager = new WindowMagnificationManager(mContext, new Object(),
-                mock(WindowMagnificationManager.Callback.class), mMockTrace,
+        mMagnificationConnectionManager = new MagnificationConnectionManager(mContext, new Object(),
+                mock(MagnificationConnectionManager.Callback.class), mMockTrace,
                 new MagnificationScaleProvider(mContext));
         mMockConnection = new MockWindowMagnificationConnection();
         mWindowMagnificationGestureHandler = new SpyWindowMagnificationGestureHandler(
-                mContext, mWindowMagnificationManager, mMockTrace, mMockCallback,
+                mContext, mMagnificationConnectionManager, mMockTrace, mMockCallback,
                 /** detectSingleFingerTripleTap= */ true, /** detectTwoFingerTripleTap= */ true,
                 /** detectShortcutTrigger= */ true, DISPLAY_0);
         mMockWindowMagnificationGestureHandler =
                 mWindowMagnificationGestureHandler.getMockGestureHandler();
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationGestureHandler.setNext(strictMock(EventStreamTransformation.class));
     }
 
     @After
     public void tearDown() {
-        mWindowMagnificationManager.disableWindowMagnification(DISPLAY_0, true);
+        mMagnificationConnectionManager.disableWindowMagnification(DISPLAY_0, true);
     }
 
     @Test
@@ -378,7 +378,7 @@
             }
             break;
             case STATE_SHOW_MAGNIFIER_SHORTCUT: {
-                mWindowMagnificationManager.disableWindowMagnification(DISPLAY_0, false);
+                mMagnificationConnectionManager.disableWindowMagnification(DISPLAY_0, false);
             }
             break;
             case STATE_TWO_FINGERS_DOWN: {
@@ -423,7 +423,7 @@
     }
 
     private boolean isWindowMagnifierEnabled(int displayId) {
-        return mWindowMagnificationManager.isWindowMagnifierEnabled(displayId);
+        return mMagnificationConnectionManager.isWindowMagnifierEnabled(displayId);
     }
 
     private static String stateToString(int state) {
@@ -495,13 +495,14 @@
         private final WindowMagnificationGestureHandler mMockWindowMagnificationGestureHandler;
 
         SpyWindowMagnificationGestureHandler(@UiContext Context context,
-                WindowMagnificationManager windowMagnificationMgr,
+                MagnificationConnectionManager magnificationConnectionManager,
                 AccessibilityTraceManager trace,
                 Callback callback,
                 boolean detectSingleFingerTripleTap, boolean detectTwoFingerTripleTap,
                 boolean detectShortcutTrigger, int displayId) {
-            super(context, windowMagnificationMgr, trace, callback, detectSingleFingerTripleTap,
-                    detectTwoFingerTripleTap, detectShortcutTrigger, displayId);
+            super(context, magnificationConnectionManager, trace, callback,
+                    detectSingleFingerTripleTap, detectTwoFingerTripleTap,
+                    detectShortcutTrigger, displayId);
             mMockWindowMagnificationGestureHandler = mock(WindowMagnificationGestureHandler.class);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
deleted file mode 100644
index 24ad976..0000000
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ /dev/null
@@ -1,847 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.accessibility.magnification;
-
-import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY;
-import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY_2;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.notNull;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
-
-import static java.lang.Float.NaN;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnectionCallback;
-import android.view.accessibility.MagnificationAnimationCallback;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.FlakyTest;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
-import com.android.server.accessibility.AccessibilityTraceManager;
-import com.android.server.statusbar.StatusBarManagerInternal;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-
-/**
- * Tests for WindowMagnificationManager.
- */
-public class WindowMagnificationManagerTest {
-
-    private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
-    private static final int SERVICE_ID = 1;
-
-    private MockWindowMagnificationConnection mMockConnection;
-    @Mock
-    private Context mContext;
-    @Mock
-    private AccessibilityTraceManager mMockTrace;
-    @Mock
-    private StatusBarManagerInternal mMockStatusBarManagerInternal;
-    @Mock
-    private MagnificationAnimationCallback mAnimationCallback;
-    @Mock
-    private WindowMagnificationManager.Callback mMockCallback;
-    private MockContentResolver mResolver;
-    private WindowMagnificationManager mWindowMagnificationManager;
-
-    @Before
-    public void setUp() throws RemoteException {
-        MockitoAnnotations.initMocks(this);
-        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
-        LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal);
-        mResolver = new MockContentResolver();
-        mMockConnection = new MockWindowMagnificationConnection();
-        mWindowMagnificationManager = new WindowMagnificationManager(mContext, new Object(),
-                mMockCallback, mMockTrace, new MagnificationScaleProvider(mContext));
-
-        when(mContext.getContentResolver()).thenReturn(mResolver);
-        stubSetConnection(false);
-
-        mResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        Settings.Secure.putFloatForUser(mResolver,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.5f,
-                CURRENT_USER_ID);
-    }
-
-    private void stubSetConnection(boolean needDelay) {
-        doAnswer((InvocationOnMock invocation) -> {
-            final boolean connect = (Boolean) invocation.getArguments()[0];
-            // Simulates setConnection() called by another process.
-            if (needDelay) {
-                final Context context = ApplicationProvider.getApplicationContext();
-                context.getMainThreadHandler().postDelayed(
-                        () -> {
-                            mWindowMagnificationManager.setConnection(
-                                    connect ? mMockConnection.getConnection() : null);
-                        }, 10);
-            } else {
-                mWindowMagnificationManager.setConnection(
-                        connect ? mMockConnection.getConnection() : null);
-            }
-            return true;
-        }).when(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(anyBoolean());
-    }
-
-    @Test
-    public void setConnection_connectionIsNull_wrapperIsNullAndLinkToDeath() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        assertNotNull(mWindowMagnificationManager.mConnectionWrapper);
-        verify(mMockConnection.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
-    }
-
-    @Test
-    public void setConnection_connectionIsNull_setMirrorWindowCallbackAndHasWrapper()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-
-        assertNotNull(mWindowMagnificationManager.mConnectionWrapper);
-        verify(mMockConnection.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
-        verify(mMockConnection.getConnection()).setConnectionCallback(
-                any(IWindowMagnificationConnectionCallback.class));
-    }
-
-    @Test
-    public void binderDied_hasConnection_wrapperIsNullAndUnlinkToDeath() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-
-        mMockConnection.getDeathRecipient().binderDied();
-
-        assertNull(mWindowMagnificationManager.mConnectionWrapper);
-        verify(mMockConnection.asBinder()).unlinkToDeath(mMockConnection.getDeathRecipient(),
-                0);
-    }
-
-    /**
-     * This test simulates {@link WindowMagnificationManager#setConnection} is called by thread A
-     * and then the former connection is called by thread B. In this situation we should keep the
-     * new connection.
-     */
-    @Test
-    public void setSecondConnectionAndFormerConnectionBinderDead_hasWrapperAndNotCallUnlinkToDeath()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        MockWindowMagnificationConnection secondConnection =
-                new MockWindowMagnificationConnection();
-
-        mWindowMagnificationManager.setConnection(secondConnection.getConnection());
-        mMockConnection.getDeathRecipient().binderDied();
-
-        assertNotNull(mWindowMagnificationManager.mConnectionWrapper);
-        verify(mMockConnection.asBinder()).unlinkToDeath(mMockConnection.getDeathRecipient(), 0);
-        verify(secondConnection.asBinder(), never()).unlinkToDeath(
-                secondConnection.getDeathRecipient(), 0);
-    }
-
-    @Test
-    public void setNullConnection_hasConnection_wrapperIsNull() throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-
-        mWindowMagnificationManager.setConnection(null);
-
-        assertNull(mWindowMagnificationManager.mConnectionWrapper);
-        verify(mMockConnection.getConnection()).setConnectionCallback(null);
-    }
-
-    @Test
-    public void enableWithAnimation_hasConnection_enableWindowMagnification()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f);
-
-        verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(2f),
-                eq(200f), eq(300f), eq(0f), eq(0f), notNull());
-    }
-
-    @Test
-    public void enableWithCallback_hasConnection_enableWindowMagnification()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f,
-                mAnimationCallback, SERVICE_ID);
-
-        verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(2f),
-                eq(200f), eq(300f), eq(0f), eq(0f),
-                any(IRemoteMagnificationAnimationCallback.class));
-        verify(mAnimationCallback).onResult(true);
-    }
-
-    @Test
-    public void disable_hasConnectionAndEnabled_disableWindowMagnification()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
-
-        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
-
-        verify(mMockConnection.getConnection()).disableWindowMagnification(eq(TEST_DISPLAY),
-                notNull());
-    }
-
-    @Test
-    public void disableWithCallback_hasConnectionAndEnabled_disableWindowMagnification()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
-
-        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false,
-                mAnimationCallback);
-
-        verify(mMockConnection.getConnection()).disableWindowMagnification(eq(TEST_DISPLAY),
-                any(IRemoteMagnificationAnimationCallback.class));
-        verify(mAnimationCallback).onResult(true);
-    }
-
-    @Test
-    public void isWindowMagnifierEnabled_hasConnectionAndEnabled_returnExpectedValue() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
-
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN);
-
-        assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
-    }
-
-    @Test
-    public void getPersistedScale() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-
-        assertEquals(mWindowMagnificationManager.getPersistedScale(TEST_DISPLAY), 2.5f);
-    }
-
-    @Test
-    public void persistScale_setValue_expectedValueInProvider() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, NaN, NaN);
-        mWindowMagnificationManager.setScale(TEST_DISPLAY, 2.5f);
-
-        mWindowMagnificationManager.persistScale(TEST_DISPLAY);
-
-        assertEquals(Settings.Secure.getFloatForUser(mResolver,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0f,
-                CURRENT_USER_ID), 2.5f);
-    }
-
-    @Test
-    public void persistScale_setValueWhenScaleIsOne_nothingChanged() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        final float persistedScale = mWindowMagnificationManager.getPersistedScale(TEST_DISPLAY);
-
-        mWindowMagnificationManager.setScale(TEST_DISPLAY, 1.0f);
-        mWindowMagnificationManager.persistScale(TEST_DISPLAY);
-
-        assertEquals(Settings.Secure.getFloatForUser(mResolver,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0f,
-                CURRENT_USER_ID), persistedScale);
-    }
-
-    @Test
-    public void scaleSetterGetter_enabledOnTestDisplay_expectedValue() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, NaN, NaN);
-
-        mWindowMagnificationManager.setScale(TEST_DISPLAY, 2.5f);
-
-        assertEquals(mWindowMagnificationManager.getScale(TEST_DISPLAY), 2.5f);
-    }
-
-    @Test
-    public void scaleSetterGetter_scaleIsOutOfRang_getNormalizeValue() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN);
-
-        mWindowMagnificationManager.setScale(TEST_DISPLAY, 10.0f);
-
-        assertEquals(mWindowMagnificationManager.getScale(TEST_DISPLAY),
-                MagnificationScaleProvider.MAX_SCALE);
-    }
-
-    @FlakyTest(bugId = 297879435)
-    @Test
-    public void logTrackingTypingFocus_processScroll_logDuration() {
-        WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager);
-        spyWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
-        spyWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, /* shown */ true);
-
-        spyWindowMagnificationManager.processScroll(TEST_DISPLAY, 10f, 10f);
-
-        verify(spyWindowMagnificationManager).logTrackingTypingFocus(anyLong());
-    }
-
-    @Test
-    public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnifier()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
-        final Region outRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
-        final Rect requestedRect = outRegion.getBounds();
-        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
-        mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY);
-
-        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
-                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
-
-        verify(mMockConnection.getConnection(), never())
-                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
-    }
-
-
-    @Test
-    public void onRectangleOnScreenRequested_trackingDisabledByScroll_withoutMovingMagnifier()
-            throws RemoteException {
-        final float distanceX = 10f;
-        final float distanceY = 10f;
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
-        final Region outRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
-        final Rect requestedRect = outRegion.getBounds();
-        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
-        mWindowMagnificationManager.processScroll(TEST_DISPLAY, distanceX, distanceY);
-
-        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
-                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
-
-        verify(mMockConnection.getConnection(), never())
-                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
-    }
-
-    @Test
-    public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnifier()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
-        final Region outRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
-        final Rect requestedRect = outRegion.getBounds();
-        requestedRect.inset(-10, -10);
-
-        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
-                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
-
-        verify(mMockConnection.getConnection(), never())
-                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
-    }
-    @Test
-    public void onRectangleOnScreenRequested_imeVisibilityDefaultInvisible_withoutMovingMagnifier()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
-        final Region outRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
-        final Rect requestedRect = outRegion.getBounds();
-        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
-
-        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
-                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
-
-        verify(mMockConnection.getConnection(), never())
-                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
-    }
-
-    @Test
-    public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnifier()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
-        final Region outRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
-        final Rect requestedRect = outRegion.getBounds();
-        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
-
-        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
-                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
-
-        verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
-                eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
-                any(IRemoteMagnificationAnimationCallback.class));
-    }
-
-    @Test
-    public void onRectangleOnScreenRequested_imeInvisible_withoutMovingMagnifier()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
-        final Region outRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
-        final Rect requestedRect = outRegion.getBounds();
-        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, false);
-
-        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
-                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
-
-        verify(mMockConnection.getConnection(), never())
-                .moveWindowMagnifierToPosition(anyInt(), anyFloat(), anyFloat(), any());
-    }
-
-    @Test
-    public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnifier()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
-        mMockConnection.getConnectionCallback().onMove(TEST_DISPLAY);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
-        final Region outRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion);
-        final Rect requestedRect = outRegion.getBounds();
-        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
-
-        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
-                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
-
-        verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
-                eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
-                any(IRemoteMagnificationAnimationCallback.class));
-    }
-
-    @Test
-    public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnifier() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
-        final Region beforeRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
-        final Rect requestedRect = beforeRegion.getBounds();
-        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
-        mWindowMagnificationManager.setMagnificationFollowTypingEnabled(false);
-
-        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
-                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
-
-        final Region afterRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion);
-        assertEquals(afterRegion, beforeRegion);
-    }
-
-    @Test
-    public void onRectangleOnScreenRequested_trackingDisabled_withoutMovingMagnifier() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
-        mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false);
-        final Region beforeRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
-        final Rect requestedRect = beforeRegion.getBounds();
-        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
-
-        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
-                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
-
-        final Region afterRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion);
-        assertEquals(afterRegion, beforeRegion);
-    }
-
-    @Test
-    public void onRectangleOnScreenRequested_trackingDisabledAndEnabledMagnifier_movingMagnifier()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f);
-        mWindowMagnificationManager.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
-        mWindowMagnificationManager.setTrackingTypingFocusEnabled(TEST_DISPLAY, false);
-        final Region beforeRegion = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion);
-        final Rect requestedRect = beforeRegion.getBounds();
-        requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10);
-        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
-        // Enabling a window magnifier again will turn on the tracking typing focus functionality.
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, NaN, NaN, NaN);
-
-        mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY,
-                requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom);
-
-        verify(mMockConnection.getConnection()).moveWindowMagnifierToPosition(eq(TEST_DISPLAY),
-                eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()),
-                any(IRemoteMagnificationAnimationCallback.class));
-    }
-
-    @Test
-    public void moveWindowMagnifier_enabled_invokeConnectionMethod() throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN);
-
-        mWindowMagnificationManager.moveWindowMagnification(TEST_DISPLAY, 200, 300);
-        verify(mMockConnection.getConnection()).moveWindowMagnifier(TEST_DISPLAY, 200, 300);
-    }
-
-    @Test
-    public void showMagnificationButton_hasConnection_invokeConnectionMethod()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-
-        mWindowMagnificationManager.showMagnificationButton(TEST_DISPLAY,
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
-        verify(mMockConnection.getConnection()).showMagnificationButton(TEST_DISPLAY,
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
-
-        mWindowMagnificationManager.removeMagnificationButton(TEST_DISPLAY);
-        verify(mMockConnection.getConnection()).removeMagnificationButton(TEST_DISPLAY);
-    }
-
-    @Test
-    public void removeMagnificationSettingsPanel_hasConnection_invokeConnectionMethod()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-
-        mWindowMagnificationManager.removeMagnificationSettingsPanel(TEST_DISPLAY);
-        verify(mMockConnection.getConnection()).removeMagnificationSettingsPanel(TEST_DISPLAY);
-    }
-
-    @Test
-    public void onUserMagnificationScaleChanged_hasConnection_invokeConnectionMethod()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-
-        final float testScale = 3f;
-        mWindowMagnificationManager.onUserMagnificationScaleChanged(
-                CURRENT_USER_ID, TEST_DISPLAY, testScale);
-        verify(mMockConnection.getConnection()).onUserMagnificationScaleChanged(
-                eq(CURRENT_USER_ID), eq(TEST_DISPLAY), eq(testScale));
-    }
-
-    @Test
-    public void pointersInWindow_magnifierEnabled_returnCorrectValue() throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
-        mMockConnection.getConnectionCallback().onWindowMagnifierBoundsChanged(TEST_DISPLAY,
-                new Rect(0, 0, 500, 500));
-        PointF[] pointersLocation = new PointF[2];
-        pointersLocation[0] = new PointF(600, 700);
-        pointersLocation[1] = new PointF(300, 400);
-        MotionEvent event = generatePointersDownEvent(pointersLocation);
-
-        assertEquals(mWindowMagnificationManager.pointersInWindow(TEST_DISPLAY, event), 1);
-    }
-
-    @Test
-    public void onPerformScaleAction_magnifierEnabled_notifyAction() throws RemoteException {
-        final float newScale = 4.0f;
-        final boolean updatePersistence = true;
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
-
-        mMockConnection.getConnectionCallback().onPerformScaleAction(
-                TEST_DISPLAY, newScale, updatePersistence);
-
-        verify(mMockCallback).onPerformScaleAction(
-                eq(TEST_DISPLAY), eq(newScale), eq(updatePersistence));
-    }
-
-    @Test
-    public void onAccessibilityActionPerformed_magnifierEnabled_notifyAction()
-            throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
-
-        mMockConnection.getConnectionCallback().onAccessibilityActionPerformed(TEST_DISPLAY);
-
-        verify(mMockCallback).onAccessibilityActionPerformed(eq(TEST_DISPLAY));
-    }
-
-    @Test
-    public void binderDied_windowMagnifierIsEnabled_resetState() throws RemoteException {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
-
-        mMockConnection.getDeathRecipient().binderDied();
-
-        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
-    }
-
-    @Test
-    public void
-            requestConnectionToNull_disableAllMagnifiersAndRequestWindowMagnificationConnection()
-            throws RemoteException {
-        assertTrue(mWindowMagnificationManager.requestConnection(true));
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
-
-        assertTrue(mWindowMagnificationManager.requestConnection(false));
-
-        verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY, null);
-        verify(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(false);
-    }
-
-    @Test
-    public void requestConnection_requestWindowMagnificationConnection() throws RemoteException {
-        assertTrue(mWindowMagnificationManager.requestConnection(true));
-        verify(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(true);
-    }
-
-    @Test
-    public void isConnected_requestConnection_expectedValue() throws RemoteException {
-        mWindowMagnificationManager.requestConnection(true);
-        assertTrue(mWindowMagnificationManager.isConnected());
-
-        mWindowMagnificationManager.requestConnection(false);
-        assertFalse(mWindowMagnificationManager.isConnected());
-    }
-
-    @Test
-    public void requestConnection_registerAndUnregisterBroadcastReceiver() {
-        assertTrue(mWindowMagnificationManager.requestConnection(true));
-        verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
-
-        assertTrue(mWindowMagnificationManager.requestConnection(false));
-        verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
-    }
-
-    @Test
-    public void requestConnectionToNull_expectedGetterResults() {
-        mWindowMagnificationManager.requestConnection(true);
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1);
-
-        mWindowMagnificationManager.requestConnection(false);
-
-        assertEquals(1f, mWindowMagnificationManager.getScale(TEST_DISPLAY), 0);
-        assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterX(TEST_DISPLAY)));
-        assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterY(TEST_DISPLAY)));
-        final Region bounds = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, bounds);
-        assertTrue(bounds.isEmpty());
-    }
-
-    @Test
-    public void enableWindowMagnification_connecting_invokeConnectionMethodAfterConnected()
-            throws RemoteException {
-        stubSetConnection(true);
-        mWindowMagnificationManager.requestConnection(true);
-
-        assertTrue(mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1));
-
-        // Invoke enableWindowMagnification if the connection is connected.
-        verify(mMockConnection.getConnection()).enableWindowMagnification(
-                eq(TEST_DISPLAY), eq(3f),
-                eq(1f), eq(1f), eq(0f), eq(0f), notNull());
-    }
-
-    @Test
-    public void resetAllMagnification_enabledBySameId_windowMagnifiersDisabled() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f,
-                100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID);
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY_2, 3f,
-                100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID);
-        assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
-        assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2));
-
-        mWindowMagnificationManager.resetAllIfNeeded(SERVICE_ID);
-
-        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
-        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2));
-    }
-
-    @Test
-    public void resetAllMagnification_enabledByDifferentId_windowMagnifierDisabled() {
-        final int serviceId2 = SERVICE_ID + 1;
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f,
-                100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID);
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY_2, 3f,
-                100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, serviceId2);
-        assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
-        assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2));
-
-        mWindowMagnificationManager.resetAllIfNeeded(SERVICE_ID);
-
-        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
-        assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2));
-    }
-
-    @Test
-    public void onScreenOff_windowMagnifierIsEnabled_removeButtonAndDisableWindowMagnification()
-            throws RemoteException {
-        mWindowMagnificationManager.requestConnection(true);
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.5f, NaN, NaN);
-
-        mWindowMagnificationManager.mScreenStateReceiver.onReceive(mContext,
-                new Intent(Intent.ACTION_SCREEN_OFF));
-
-        verify(mMockConnection.getConnection()).removeMagnificationButton(TEST_DISPLAY);
-        verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY, null);
-        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
-    }
-
-    @Test
-    public void centerGetter_enabledOnTestDisplay_expectedValues() {
-        mWindowMagnificationManager.requestConnection(true);
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f);
-
-        assertEquals(mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 100f);
-        assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f);
-    }
-
-    @Test
-    public void centerGetter_enabledOnTestDisplayWindowAtCenter_expectedValues()
-            throws RemoteException {
-        mWindowMagnificationManager.requestConnection(true);
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f,
-                100f, 200f, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER);
-
-        assertEquals(mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 100f);
-        assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f);
-
-        verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f),
-                eq(100f), eq(200f), eq(0f), eq(0f), notNull());
-    }
-
-    @Test
-    public void centerGetter_enabledOnTestDisplayWindowAtLeftTop_expectedValues()
-            throws RemoteException {
-        mWindowMagnificationManager.requestConnection(true);
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f,
-                100f, 200f, WindowMagnificationManager.WINDOW_POSITION_AT_TOP_LEFT);
-
-        assertEquals(mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 100f);
-        assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f);
-
-        verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f),
-                eq(100f), eq(200f), eq(-1f), eq(-1f), notNull());
-    }
-
-    @Test
-    public void magnifierGetters_disabled_expectedValues() {
-        mWindowMagnificationManager.requestConnection(true);
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f,
-                100f, 200f, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER);
-
-        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
-
-        assertEquals(1f, mWindowMagnificationManager.getScale(TEST_DISPLAY), 0);
-        assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterX(TEST_DISPLAY)));
-        assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterY(TEST_DISPLAY)));
-        final Region bounds = new Region();
-        mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, bounds);
-        assertTrue(bounds.isEmpty());
-    }
-
-    @Test
-    public void onDisplayRemoved_enabledOnTestDisplay_disabled() {
-        mWindowMagnificationManager.requestConnection(true);
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f);
-
-        mWindowMagnificationManager.onDisplayRemoved(TEST_DISPLAY);
-
-        assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
-    }
-
-    @Test
-    public void onWindowMagnificationActivationState_magnifierEnabled_notifyActivatedState() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
-
-        verify(mMockCallback).onWindowMagnificationActivationState(TEST_DISPLAY, true);
-    }
-
-    @Test
-    public void onWindowMagnificationActivationState_magnifierDisabled_notifyDeactivatedState() {
-        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
-        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
-
-        verify(mMockCallback).onWindowMagnificationActivationState(TEST_DISPLAY, false);
-
-        Mockito.reset(mMockCallback);
-        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
-
-        verify(mMockCallback, never()).onWindowMagnificationActivationState(eq(TEST_DISPLAY),
-                anyBoolean());
-    }
-
-    private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) {
-        final int len = pointersLocation.length;
-
-        final MotionEvent.PointerProperties[] pp = new MotionEvent.PointerProperties[len];
-        for (int i = 0; i < len; i++) {
-            MotionEvent.PointerProperties pointerProperty = new MotionEvent.PointerProperties();
-            pointerProperty.id = i;
-            pointerProperty.toolType = MotionEvent.TOOL_TYPE_FINGER;
-            pp[i] = pointerProperty;
-        }
-
-        final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[len];
-        for (int i = 0; i < len; i++) {
-            MotionEvent.PointerCoords pointerCoord = new MotionEvent.PointerCoords();
-            pointerCoord.x = pointersLocation[i].x;
-            pointerCoord.y = pointersLocation[i].y;
-            pc[i] = pointerCoord;
-        }
-
-        return MotionEvent.obtain(
-                /* downTime */ SystemClock.uptimeMillis(),
-                /* eventTime */ SystemClock.uptimeMillis(),
-                /* action */ MotionEvent.ACTION_POINTER_DOWN,
-                /* pointerCount */ pc.length,
-                /* pointerProperties */ pp,
-                /* pointerCoords */ pc,
-                /* metaState */ 0,
-                /* buttonState */ 0,
-                /* xPrecision */ 1.0f,
-                /* yPrecision */ 1.0f,
-                /* deviceId */ 0,
-                /* edgeFlags */ 0,
-                /* source */ InputDevice.SOURCE_TOUCHSCREEN,
-                /* flags */ 0);
-    }
-
-
-}
diff --git a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
index 749b07d..9c8276a 100644
--- a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
@@ -19,6 +19,16 @@
 import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID;
 import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4;
 import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D;
+import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE;
+import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION;
+import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
+
+import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_LARGE;
+import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_MEDIUM;
+import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_SMALL;
+import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_UNKNOWN;
+
+import static junit.framework.Assert.assertEquals;
 
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -34,11 +44,15 @@
 import android.media.LoudnessCodecInfo;
 import android.media.PlayerBase;
 import android.os.IBinder;
+import android.os.PersistableBundle;
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.server.audio.LoudnessCodecHelper.DeviceSplRange;
+import com.android.server.audio.LoudnessCodecHelper.LoudnessCodecInputProperties;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -84,8 +98,7 @@
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
 
         mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
-                List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true,
-                        CODEC_METADATA_TYPE_MPEG_4)));
+                List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4)));
 
         verify(mDispatcher).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), any());
     }
@@ -96,8 +109,7 @@
         mLoudnessHelper.unregisterLoudnessCodecUpdatesDispatcher(mDispatcher);
 
         mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
-                List.of(getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/false,
-                        CODEC_METADATA_TYPE_MPEG_D)));
+                List.of(getLoudnessInfo(/*isDownmixing=*/false, CODEC_METADATA_TYPE_MPEG_D)));
 
         verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
                 any());
@@ -108,11 +120,9 @@
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
 
         mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
-                List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true,
-                        CODEC_METADATA_TYPE_MPEG_4)));
-        mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid,
-                getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true,
-                        CODEC_METADATA_TYPE_MPEG_D));
+                List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4)));
+        mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, /*mediaCodecHash=*/222,
+                getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
 
         verify(mDispatcher, times(2)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
                 any());
@@ -124,11 +134,10 @@
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
 
         mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
-                List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true,
+                List.of(getLoudnessInfo(/*isDownmixing=*/true,
                         CODEC_METADATA_TYPE_MPEG_4)));
-        mLoudnessHelper.addLoudnessCodecInfo(newPiid,
-                getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true,
-                        CODEC_METADATA_TYPE_MPEG_D));
+        mLoudnessHelper.addLoudnessCodecInfo(newPiid, /*mediaCodecHash=*/222,
+                getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
 
         verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
                 any());
@@ -140,12 +149,10 @@
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
 
         mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
-                List.of(getLoudnessInfo(/*mediaCodecHash=*/111, /*isDownmixing=*/true,
-                        CODEC_METADATA_TYPE_MPEG_4)));
+                List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4)));
         //does not trigger dispatch since active apc list does not contain newPiid
         mLoudnessHelper.startLoudnessCodecUpdates(newPiid,
-                List.of(getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true,
-                        CODEC_METADATA_TYPE_MPEG_D)));
+                List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D)));
         verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
                 any());
 
@@ -157,9 +164,8 @@
     @Test
     public void updateCodecParameters_noStartedPiids_noDispatch() throws Exception {
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
-        mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid,
-                getLoudnessInfo(/*mediaCodecHash=*/222, /*isDownmixing=*/true,
-                        CODEC_METADATA_TYPE_MPEG_D));
+        mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, /*mediaCodecHash=*/222,
+                getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
 
         mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid));
 
@@ -170,8 +176,8 @@
 
     @Test
     public void updateCodecParameters_removedCodecInfo_noDispatch() throws Exception {
-        final LoudnessCodecInfo info = getLoudnessInfo(/*mediaCodecHash=*/111,
-                /*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4);
+        final LoudnessCodecInfo info = getLoudnessInfo(/*isDownmixing=*/true,
+                CODEC_METADATA_TYPE_MPEG_4);
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
 
         mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info));
@@ -186,8 +192,8 @@
 
     @Test
     public void updateCodecParameters_stoppedPiids_noDispatch() throws Exception {
-        final LoudnessCodecInfo info = getLoudnessInfo(/*mediaCodecHash=*/111,
-                /*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4);
+        final LoudnessCodecInfo info = getLoudnessInfo(/*isDownmixing=*/true,
+                CODEC_METADATA_TYPE_MPEG_4);
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
 
         mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info));
@@ -200,6 +206,108 @@
                 any());
     }
 
+    @Test
+    public void checkParcelableBundle_forMpeg4CodecInputProperties() {
+        PersistableBundle loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/true,
+                SPL_RANGE_SMALL).createLoudnessParameters();
+        assertEquals(64, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(1, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/false,
+                SPL_RANGE_SMALL).createLoudnessParameters();
+        assertEquals(64, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(1, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/true,
+                SPL_RANGE_MEDIUM).createLoudnessParameters();
+        assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(1, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/false,
+                SPL_RANGE_MEDIUM).createLoudnessParameters();
+        assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(0, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/true,
+                SPL_RANGE_LARGE).createLoudnessParameters();
+        assertEquals(124, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(0, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/false,
+                SPL_RANGE_LARGE).createLoudnessParameters();
+        assertEquals(124, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(0, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/true,
+                SPL_RANGE_UNKNOWN).createLoudnessParameters();
+        assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(1, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_4, /*isDownmixing*/false,
+                SPL_RANGE_UNKNOWN).createLoudnessParameters();
+        assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(0, loudnessParameters.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION));
+    }
+
+    @Test
+    public void checkParcelableBundle_forMpegDCodecInputProperties() {
+        PersistableBundle loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/true,
+                SPL_RANGE_SMALL).createLoudnessParameters();
+        assertEquals(64, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(3, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/false,
+                SPL_RANGE_SMALL).createLoudnessParameters();
+        assertEquals(64, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(3, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/true,
+                SPL_RANGE_MEDIUM).createLoudnessParameters();
+        assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/false,
+                SPL_RANGE_MEDIUM).createLoudnessParameters();
+        assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/true,
+                SPL_RANGE_LARGE).createLoudnessParameters();
+        assertEquals(124, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/false,
+                SPL_RANGE_LARGE).createLoudnessParameters();
+        assertEquals(124, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/true,
+                SPL_RANGE_UNKNOWN).createLoudnessParameters();
+        assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE));
+
+        loudnessParameters = createInputProperties(
+                CODEC_METADATA_TYPE_MPEG_D, /*isDownmixing*/false,
+                SPL_RANGE_UNKNOWN).createLoudnessParameters();
+        assertEquals(96, loudnessParameters.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL));
+        assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE));
+    }
+
     private List<AudioPlaybackConfiguration> getApcListForPiids(int... piids) {
         final ArrayList<AudioPlaybackConfiguration> apcList = new ArrayList<>();
 
@@ -220,11 +328,15 @@
         return apcList;
     }
 
-    private static LoudnessCodecInfo getLoudnessInfo(int mediaCodecHash, boolean isDownmixing,
-            int metadataType) {
+    private static LoudnessCodecInputProperties createInputProperties(
+            int metadataType, boolean isDownmixing, @DeviceSplRange int splRange) {
+        return new LoudnessCodecInputProperties.Builder().setMetadataType(
+                metadataType).setIsDownmixing(isDownmixing).setDeviceSplRange(splRange).build();
+    }
+
+    private static LoudnessCodecInfo getLoudnessInfo(boolean isDownmixing, int metadataType) {
         LoudnessCodecInfo info = new LoudnessCodecInfo();
         info.isDownmixing = isDownmixing;
-        info.mediaCodecHashCode = mediaCodecHash;
         info.metadataType = metadataType;
 
         return info;
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index b5ba322..9213601a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -413,18 +413,24 @@
     public void getDeviceIdForDisplayId_invalidDisplayId_returnsDefault() {
         assertThat(mVdm.getDeviceIdForDisplayId(Display.INVALID_DISPLAY))
                 .isEqualTo(DEVICE_ID_DEFAULT);
+        assertThat(mLocalService.getDeviceIdForDisplayId(Display.INVALID_DISPLAY))
+                .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
     public void getDeviceIdForDisplayId_defaultDisplayId_returnsDefault() {
         assertThat(mVdm.getDeviceIdForDisplayId(Display.DEFAULT_DISPLAY))
                 .isEqualTo(DEVICE_ID_DEFAULT);
+        assertThat(mLocalService.getDeviceIdForDisplayId(Display.DEFAULT_DISPLAY))
+                .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
     public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() {
         assertThat(mVdm.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID))
                 .isEqualTo(DEVICE_ID_DEFAULT);
+        assertThat(mLocalService.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID))
+                .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
@@ -433,6 +439,8 @@
 
         assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
                 .isEqualTo(mDeviceImpl.getDeviceId());
+        assertThat(mLocalService.getDeviceIdForDisplayId(DISPLAY_ID_1))
+                .isEqualTo(mDeviceImpl.getDeviceId());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 01922e0..edfe1b4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -40,6 +40,7 @@
 import android.testing.TestableLooper;
 import android.view.Surface;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -77,6 +78,11 @@
         when(mVirtualCameraServiceMock.registerCamera(any(), any())).thenReturn(true);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        mVirtualCameraController.close();
+    }
+
     @Test
     public void registerCamera_registersCamera() throws Exception {
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
@@ -95,6 +101,8 @@
     public void unregisterCamera_unregistersCamera() throws Exception {
         VirtualCameraConfig config = createVirtualCameraConfig(
                 CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1);
+        mVirtualCameraController.registerCamera(config);
+
         mVirtualCameraController.unregisterCamera(config);
 
         verify(mVirtualCameraServiceMock).unregisterCamera(any());
@@ -107,9 +115,10 @@
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
                 CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_2));
 
+        mVirtualCameraController.close();
+
         ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
                 ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
-        mVirtualCameraController.close();
         verify(mVirtualCameraServiceMock, times(2)).registerCamera(any(),
                 configurationCaptor.capture());
         List<VirtualCameraConfiguration> virtualCameraConfigurations =
diff --git a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
deleted file mode 100644
index 5aef7a3..0000000
--- a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.media.AudioManager;
-import android.media.AudioRoutesInfo;
-import android.media.IAudioRoutesObserver;
-import android.media.MediaRoute2Info;
-import android.os.RemoteException;
-
-import com.android.internal.R;
-import com.android.server.audio.AudioService;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(JUnit4.class)
-public class AudioPoliciesDeviceRouteControllerTest {
-
-    private static final String ROUTE_NAME_DEFAULT = "default";
-    private static final String ROUTE_NAME_DOCK = "dock";
-    private static final String ROUTE_NAME_HEADPHONES = "headphones";
-
-    private static final int VOLUME_SAMPLE_1 = 25;
-
-    @Mock
-    private Context mContext;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private AudioManager mAudioManager;
-    @Mock
-    private AudioService mAudioService;
-    @Mock
-    private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
-
-    @Captor
-    private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor;
-
-    private AudioPoliciesDeviceRouteController mController;
-
-    private IAudioRoutesObserver.Stub mAudioRoutesObserver;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mResources.getText(anyInt())).thenReturn(ROUTE_NAME_DEFAULT);
-
-        // Setting built-in speaker as default speaker.
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER;
-        when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture()))
-                .thenReturn(audioRoutesInfo);
-
-        mController = new AudioPoliciesDeviceRouteController(
-                mContext, mAudioManager, mAudioService, mOnDeviceRouteChangedListener);
-
-        mAudioRoutesObserver = mAudioRoutesObserverCaptor.getValue();
-    }
-
-    @Test
-    public void getDeviceRoute_noSelectedRoutes_returnsDefaultDevice() {
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DEFAULT);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
-    }
-
-    @Test
-    public void getDeviceRoute_audioRouteHasChanged_returnsRouteFromAudioService() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
-    }
-
-    @Test
-    public void getDeviceRoute_selectDevice_returnsSelectedRoute() {
-        when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
-                .thenReturn(ROUTE_NAME_DOCK);
-
-        mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
-    }
-
-    @Test
-    public void getDeviceRoute_hasSelectedAndAudioServiceRoutes_returnsSelectedRoute() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-        when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
-                .thenReturn(ROUTE_NAME_DOCK);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
-    }
-
-    @Test
-    public void getDeviceRoute_unselectRoute_returnsAudioServiceRoute() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-        when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
-                .thenReturn(ROUTE_NAME_DOCK);
-
-        mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        mController.selectRoute(null);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
-    }
-
-    @Test
-    public void getDeviceRoute_selectRouteFails_returnsAudioServiceRoute() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
-    }
-
-    @Test
-    public void selectRoute_selectWiredRoute_returnsTrue() {
-        assertThat(mController.selectRoute(MediaRoute2Info.TYPE_HDMI)).isTrue();
-    }
-
-    @Test
-    public void selectRoute_selectBluetoothRoute_returnsFalse() {
-        assertThat(mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)).isFalse();
-    }
-
-    @Test
-    public void selectRoute_unselectRoute_returnsTrue() {
-        assertThat(mController.selectRoute(null)).isTrue();
-    }
-
-    @Test
-    public void updateVolume_noSelectedRoute_deviceRouteVolumeChanged() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        mController.updateVolume(VOLUME_SAMPLE_1);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
-        assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
-    }
-
-    @Test
-    public void updateVolume_connectSelectedRouteLater_selectedRouteVolumeChanged() {
-        when(mResources.getText(R.string.default_audio_route_name_headphones))
-                .thenReturn(ROUTE_NAME_HEADPHONES);
-        when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
-                .thenReturn(ROUTE_NAME_DOCK);
-
-        AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
-        audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
-        callAudioRoutesObserver(audioRoutesInfo);
-
-        mController.updateVolume(VOLUME_SAMPLE_1);
-
-        mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
-        MediaRoute2Info route2Info = mController.getSelectedRoute();
-        assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
-        assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
-    }
-
-    /**
-     * Simulates {@link IAudioRoutesObserver.Stub#dispatchAudioRoutesChanged(AudioRoutesInfo)}
-     * from {@link AudioService}. This happens when there is a wired route change,
-     * like a wired headset being connected.
-     *
-     * @param audioRoutesInfo updated state of connected wired device
-     */
-    private void callAudioRoutesObserver(AudioRoutesInfo audioRoutesInfo) {
-        try {
-            // this is a captured observer implementation
-            // from WiredRoutesController's AudioService#startWatchingRoutes call
-            mAudioRoutesObserver.dispatchAudioRoutesChanged(audioRoutesInfo);
-        } catch (RemoteException exception) {
-            // Should not happen since the object is mocked.
-            assertWithMessage("An unexpected RemoteException happened.").fail();
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
index 14b121d..0961b7d 100644
--- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -19,6 +19,7 @@
 import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
 
 import android.content.Context;
+import android.os.Looper;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -56,7 +57,8 @@
     @RequiresFlagsDisabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
     public void createInstance_audioPoliciesFlagIsDisabled_createsLegacyController() {
         DeviceRouteController deviceRouteController =
-                DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener);
+                DeviceRouteController.createInstance(
+                        mContext, Looper.getMainLooper(), mOnDeviceRouteChangedListener);
 
         Truth.assertThat(deviceRouteController).isInstanceOf(LegacyDeviceRouteController.class);
     }
@@ -65,7 +67,8 @@
     @RequiresFlagsEnabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
     public void createInstance_audioPoliciesFlagIsEnabled_createsAudioPoliciesController() {
         DeviceRouteController deviceRouteController =
-                DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener);
+                DeviceRouteController.createInstance(
+                        mContext, Looper.getMainLooper(), mOnDeviceRouteChangedListener);
 
         Truth.assertThat(deviceRouteController)
                 .isInstanceOf(AudioPoliciesDeviceRouteController.class);
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 6ae2658..cd29c80 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -24,11 +24,13 @@
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
+import android.app.usage.Flags;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.os.PersistableBundle;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.AtomicFile;
 import android.util.LongSparseArray;
@@ -183,6 +185,17 @@
                 case Event.LOCUS_ID_SET:
                     event.mLocusId = "locus" + (i % 7); //"random" locus
                     break;
+                case Event.USER_INTERACTION:
+                    if (Flags.userInteractionTypeApi()) {
+                        // "random" user interaction extras.
+                        PersistableBundle extras = new PersistableBundle();
+                        extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY,
+                                "fake.namespace.category" + (i % 13));
+                        extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION,
+                                "fakeaction" + (i % 13));
+                        event.mExtras = extras;
+                    }
+                    break;
             }
 
             mIntervalStats.addEvent(event);
@@ -295,6 +308,18 @@
                         assertEquals(e1.mLocusIdToken, e2.mLocusIdToken,
                                 "Usage event " + debugId);
                         break;
+                    case Event.USER_INTERACTION:
+                        if (Flags.userInteractionTypeApi()) {
+                            PersistableBundle extras1 = e1.getExtras();
+                            PersistableBundle extras2 = e2.getExtras();
+                            assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY),
+                                    extras2.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY),
+                                    "Usage event " + debugId);
+                            assertEquals(extras1.getString(UsageStatsManager.EXTRA_EVENT_ACTION),
+                                    extras2.getString(UsageStatsManager.EXTRA_EVENT_ACTION),
+                                    "Usage event " + debugId);
+                        }
+                        break;
                 }
                 // fallthrough
             case 4: // test fields added in version 4
diff --git a/services/tests/servicestests/src/com/android/server/utils/UserSettingDeviceConfigMediatorTest.java b/services/tests/servicestests/src/com/android/server/utils/UserSettingDeviceConfigMediatorTest.java
new file mode 100644
index 0000000..377e4c3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/utils/UserSettingDeviceConfigMediatorTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import android.provider.DeviceConfig;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link UserSettingDeviceConfigMediator}
+ */
+@RunWith(AndroidJUnit4.class)
+public class UserSettingDeviceConfigMediatorTest {
+    @Test
+    public void testDeviceConfigOnly() {
+        UserSettingDeviceConfigMediator mediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(',');
+
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder("test")
+                .setInt("int", 1)
+                .setFloat("float", .5f)
+                .setBoolean("boolean", true)
+                .setLong("long", 123456789)
+                .setString("string", "abc123")
+                .build();
+
+        mediator.setDeviceConfigProperties(properties);
+
+        assertEquals(1, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("invalidKey", 123));
+        assertEquals(.5f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001);
+        assertEquals(true, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("invalidKey", true));
+        assertEquals(123456789, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getInt("invalidKey", 987654321));
+        assertEquals("abc123", mediator.getString("string", "xyz987"));
+        assertEquals("xyz987", mediator.getString("invalidKey", "xyz987"));
+
+        // Clear the properties
+        mediator.setDeviceConfigProperties(null);
+
+        assertEquals(123, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("invalidKey", 123));
+        assertEquals(.8f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001);
+        assertEquals(false, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("invalidKey", true));
+        assertEquals(987654321, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getInt("invalidKey", 987654321));
+        assertEquals("xyz987", mediator.getString("string", "xyz987"));
+        assertEquals("xyz987", mediator.getString("invalidKey", "xyz987"));
+    }
+
+    @Test
+    public void testSettingsOnly() {
+        UserSettingDeviceConfigMediator mediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(',');
+
+        String settings = "int=1,float=.5f,boolean=true,long=123456789,string=abc123";
+
+        mediator.setSettingsString(settings);
+
+        assertEquals(1, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("invalidKey", 123));
+        assertEquals(.5f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001);
+        assertEquals(true, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("invalidKey", true));
+        assertEquals(123456789, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getInt("invalidKey", 987654321));
+        assertEquals("abc123", mediator.getString("string", "xyz987"));
+        assertEquals("xyz987", mediator.getString("invalidKey", "xyz987"));
+
+        // Clear the settings
+        mediator.setSettingsString(null);
+
+        assertEquals(123, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("invalidKey", 123));
+        assertEquals(.8f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("invalidKey", .8f), 0.001);
+        assertEquals(false, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("invalidKey", true));
+        assertEquals(987654321, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getInt("invalidKey", 987654321));
+        assertEquals("xyz987", mediator.getString("string", "xyz987"));
+        assertEquals("xyz987", mediator.getString("invalidKey", "xyz987"));
+    }
+
+    @Test
+    public void testSettingsOverridesAll() {
+        UserSettingDeviceConfigMediator mediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesAllMediator(',');
+
+        String settings = "int=1,float=.5f,boolean=true,long=123456789,string=abc123,"
+                + "intOnlyInSettings=9,floatOnlyInSettings=.25f,booleanOnlyInSettings=true,"
+                + "longOnlyInSettings=53771465,stringOnlyInSettings=settingsString";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder("test")
+                .setInt("int", 10)
+                .setInt("intOnlyInDeviceConfig", 9001)
+                .setFloat("float", .7f)
+                .setFloat("floatOnlyInDeviceConfig", .9f)
+                .setBoolean("boolean", false)
+                .setBoolean("booleanOnlyInDeviceConfig", true)
+                .setLong("long", 60000001)
+                .setLong("longOnlyInDeviceConfig", 7357)
+                .setString("string", "xyz987")
+                .setString("stringOnlyInDeviceConfig", "deviceConfigString")
+                .build();
+
+        mediator.setSettingsString(settings);
+        mediator.setDeviceConfigProperties(properties);
+
+        // Since settings overrides all, anything in DeviceConfig should be ignored,
+        // even if settings doesn't have a value for it.
+        assertEquals(1, mediator.getInt("int", 123));
+        assertEquals(9, mediator.getInt("intOnlyInSettings", 123));
+        assertEquals(123, mediator.getInt("intOnlyInDeviceConfig", 123));
+        assertEquals(.5f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.25f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001);
+        assertEquals(true, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("booleanOnlyInSettings", false));
+        assertEquals(false, mediator.getBoolean("booleanOnlyInDeviceConfig", false));
+        assertEquals(123456789, mediator.getLong("long", 987654321));
+        assertEquals(53771465, mediator.getLong("longOnlyInSettings", 987654321));
+        assertEquals(987654321, mediator.getLong("longOnlyInDeviceConfig", 987654321));
+        assertEquals("abc123", mediator.getString("string", "default"));
+        assertEquals("settingsString", mediator.getString("stringOnlyInSettings", "default"));
+        assertEquals("default", mediator.getString("stringOnlyInDeviceConfig", "default"));
+
+        // Nothing in settings, do DeviceConfig can be used.
+        mediator.setSettingsString("");
+
+        assertEquals(10, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("intOnlyInSettings", 123));
+        assertEquals(9001, mediator.getInt("intOnlyInDeviceConfig", 123));
+        assertEquals(.7f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001);
+        assertEquals(.9f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001);
+        assertEquals(false, mediator.getBoolean("boolean", false));
+        assertEquals(false, mediator.getBoolean("booleanOnlyInSettings", false));
+        assertEquals(true, mediator.getBoolean("booleanOnlyInDeviceConfig", false));
+        assertEquals(60000001, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getLong("longOnlyInSettings", 987654321));
+        assertEquals(7357, mediator.getLong("longOnlyInDeviceConfig", 987654321));
+        assertEquals("xyz987", mediator.getString("string", "default"));
+        assertEquals("default", mediator.getString("stringOnlyInSettings", "default"));
+        assertEquals("deviceConfigString",
+                mediator.getString("stringOnlyInDeviceConfig", "default"));
+
+        // Nothing in settings, do DeviceConfig can be used.
+        mediator.setSettingsString(null);
+
+        assertEquals(10, mediator.getInt("int", 123));
+        assertEquals(123, mediator.getInt("intOnlyInSettings", 123));
+        assertEquals(9001, mediator.getInt("intOnlyInDeviceConfig", 123));
+        assertEquals(.7f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.8f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001);
+        assertEquals(.9f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001);
+        assertEquals(false, mediator.getBoolean("boolean", false));
+        assertEquals(false, mediator.getBoolean("booleanOnlyInSettings", false));
+        assertEquals(true, mediator.getBoolean("booleanOnlyInDeviceConfig", false));
+        assertEquals(60000001, mediator.getLong("long", 987654321));
+        assertEquals(987654321, mediator.getLong("longOnlyInSettings", 987654321));
+        assertEquals(7357, mediator.getLong("longOnlyInDeviceConfig", 987654321));
+        assertEquals("xyz987", mediator.getString("string", "default"));
+        assertEquals("default", mediator.getString("stringOnlyInSettings", "default"));
+        assertEquals("deviceConfigString",
+                mediator.getString("stringOnlyInDeviceConfig", "default"));
+    }
+
+    @Test
+    public void testSettingsOverridesIndividual() {
+        UserSettingDeviceConfigMediator mediator =
+                new UserSettingDeviceConfigMediator.SettingsOverridesIndividualMediator(',');
+
+        String settings = "int=1,float=.5f,boolean=true,long=123456789,string=abc123,"
+                + "intOnlyInSettings=9,floatOnlyInSettings=.25f,booleanOnlyInSettings=true,"
+                + "longOnlyInSettings=53771465,stringOnlyInSettings=settingsString";
+        DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder("test")
+                .setInt("int", 10)
+                .setInt("intOnlyInDeviceConfig", 9001)
+                .setFloat("float", .7f)
+                .setFloat("floatOnlyInDeviceConfig", .9f)
+                .setBoolean("boolean", false)
+                .setBoolean("booleanOnlyInDeviceConfig", true)
+                .setLong("long", 60000001)
+                .setLong("longOnlyInDeviceConfig", 7357)
+                .setString("string", "xyz987")
+                .setString("stringOnlyInDeviceConfig", "deviceConfigString")
+                .build();
+
+        mediator.setSettingsString(settings);
+        mediator.setDeviceConfigProperties(properties);
+
+        // Since settings overrides individual, anything in DeviceConfig that doesn't exist in
+        // settings should be used.
+        assertEquals(1, mediator.getInt("int", 123));
+        assertEquals(9, mediator.getInt("intOnlyInSettings", 123));
+        assertEquals(9001, mediator.getInt("intOnlyInDeviceConfig", 123));
+        assertEquals(.5f, mediator.getFloat("float", .8f), 0.001);
+        assertEquals(.25f, mediator.getFloat("floatOnlyInSettings", .8f), 0.001);
+        assertEquals(.9f, mediator.getFloat("floatOnlyInDeviceConfig", .8f), 0.001);
+        assertEquals(true, mediator.getBoolean("boolean", false));
+        assertEquals(true, mediator.getBoolean("booleanOnlyInSettings", false));
+        assertEquals(true, mediator.getBoolean("booleanOnlyInDeviceConfig", false));
+        assertEquals(123456789, mediator.getLong("long", 987654321));
+        assertEquals(53771465, mediator.getLong("longOnlyInSettings", 987654321));
+        assertEquals(7357, mediator.getLong("longOnlyInDeviceConfig", 987654321));
+        assertEquals("abc123", mediator.getString("string", "default"));
+        assertEquals("settingsString", mediator.getString("stringOnlyInSettings", "default"));
+        assertEquals("deviceConfigString",
+                mediator.getString("stringOnlyInDeviceConfig", "default"));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index d000605..32082e3 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -28,6 +28,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -1449,4 +1450,21 @@
         checkPreparationPhasesForPackage(currentSdkPackage.packageName,
                 1 /* first preparation phase */);
     }
+
+    @Test
+    @RequiresFlagsEnabled("android.webkit.update_service_v2")
+    public void testDefaultWebViewPackageIsTheFirstAvailableByDefault() {
+        String nonDefaultPackage = "nonDefaultPackage";
+        String defaultPackage1 = "defaultPackage1";
+        String defaultPackage2 = "defaultPackage2";
+        WebViewProviderInfo[] packages =
+                new WebViewProviderInfo[] {
+                    new WebViewProviderInfo(nonDefaultPackage, "", false, false, null),
+                    new WebViewProviderInfo(defaultPackage1, "", true, false, null),
+                    new WebViewProviderInfo(defaultPackage2, "", true, false, null)
+                };
+        setupWithPackages(packages);
+        assertEquals(
+                defaultPackage1, mWebViewUpdateServiceImpl.getDefaultWebViewPackage().packageName);
+    }
 }
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index f2a1fe8..c3074bb 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -93,8 +93,6 @@
                   android:showWhenLocked="true"/>
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/>
 
-        <activity android:name="com.android.server.wm.SurfaceControlViewHostTests$TestActivity" />
-
         <activity android:name="com.android.server.wm.SurfaceSyncGroupTests$TestActivity"
             android:screenOrientation="locked"
             android:turnScreenOn="true"
@@ -122,6 +120,13 @@
         <activity android:name="com.android.server.wm.ActivityRecordInputSinkTests$TestActivity"
                   android:exported="true">
         </activity>
+
+        <activity android:name="com.android.server.wm.utils.TestActivity"
+            android:screenOrientation="locked"
+            android:turnScreenOn="true"
+            android:showWhenLocked="true"
+            android:theme="@style/WhiteBackgroundTheme"
+            android:exported="true" />
     </application>
 
     <instrumentation
diff --git a/services/tests/wmtests/AndroidTest.xml b/services/tests/wmtests/AndroidTest.xml
index f8ebead..46e87dc 100644
--- a/services/tests/wmtests/AndroidTest.xml
+++ b/services/tests/wmtests/AndroidTest.xml
@@ -30,4 +30,8 @@
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false" />
     </test>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="settings put secure immersive_mode_confirmations confirmed" />
+    </target_preparer>
 </configuration>
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 2c35cf0..8d236ed 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -110,24 +110,6 @@
     }
 
     /**
-     * META + SPACE to switch keyboard layout.
-     */
-    @Test
-    public void testMetaSpace() {
-        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SPACE}, 0);
-        mPhoneWindowManager.assertSwitchKeyboardLayout(1);
-    }
-
-    /**
-     * META + SHIFT + SPACE to switch keyboard layout backwards.
-     */
-    @Test
-    public void testMetaShiftSpace() {
-        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE}, 0);
-        mPhoneWindowManager.assertSwitchKeyboardLayout(-1);
-    }
-
-    /**
      * CTRL + ALT + Z to enable accessibility service.
      */
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 71098aa..360fdf3 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -132,13 +132,6 @@
                 {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
                         new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
                         KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
-                {"Meta + Space -> Switch Keyboard Language",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_SPACE},
-                        KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_SPACE, META_ON},
-                {"Meta + Shift + Space -> Switch Keyboard Language",
-                        new int[]{META_KEY, SHIFT_KEY, KeyEvent.KEYCODE_SPACE},
-                        KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_SPACE,
-                        META_ON | SHIFT_ON},
                 {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
                         KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, META_KEY, META_ON},
                 {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 9f58491..9efbe35 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -25,8 +25,6 @@
 import static com.android.server.wm.utils.LastCallVerifier.lastCall;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -36,11 +34,14 @@
 
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
 import com.android.server.testutils.StubTransaction;
 import com.android.server.wm.utils.MockAnimationAdapter;
+import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -122,7 +123,8 @@
         }
     }
 
-    static class MockAnimationAdapterFactory extends SmoothDimmer.AnimationAdapterFactory {
+    static class MockAnimationAdapterFactory extends DimmerAnimationHelper.AnimationAdapterFactory {
+        @Override
         public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
                 SurfaceAnimationRunner runner) {
             return sTestAnimation;
@@ -175,8 +177,8 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
-        assumeTrue(Dimmer.DIMMER_REFACTOR);
         final float alpha = 0.7f;
         final int blur = 50;
         mHost.addChild(mChild, 0);
@@ -195,8 +197,8 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
-        assumeFalse(Dimmer.DIMMER_REFACTOR);
         final float alpha = 0.7f;
         mHost.addChild(mChild, 0);
         mDimmer.adjustAppearance(mChild, alpha, 20);
@@ -210,8 +212,8 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
-        assumeTrue(Dimmer.DIMMER_REFACTOR);
         mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
@@ -230,8 +232,8 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
-        assumeFalse(Dimmer.DIMMER_REFACTOR);
         mHost.addChild(mChild, 0);
 
         final float alpha = 0.8f;
@@ -290,8 +292,8 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testRemoveDimImmediately_Smooth() {
-        assumeTrue(Dimmer.DIMMER_REFACTOR);
         mHost.addChild(mChild, 0);
         mDimmer.adjustAppearance(mChild, 1, 2);
         mDimmer.adjustRelativeLayer(mChild, -1);
@@ -310,8 +312,8 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testRemoveDimImmediately_Legacy() {
-        assumeFalse(Dimmer.DIMMER_REFACTOR);
         mHost.addChild(mChild, 0);
         mDimmer.adjustAppearance(mChild, 1, 0);
         mDimmer.adjustRelativeLayer(mChild, -1);
@@ -330,8 +332,8 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
     public void testDimmerWithBlurUpdatesTransaction_Legacy() {
-        assumeFalse(Dimmer.DIMMER_REFACTOR);
         mHost.addChild(mChild, 0);
 
         final int blurRadius = 50;
@@ -344,4 +346,120 @@
         verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius);
         verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
     }
+
+    /**
+     * mChild is requesting the dim values to be set directly. In this case, dim won't play the
+     * standard animation, but directly apply mChild's requests to the dim surface
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
+    public void testContainerDimsOpeningAnimationByItself() {
+        mHost.addChild(mChild, 0);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0.1f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0.2f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0.3f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.updateDims(mTransaction);
+
+        verify(mTransaction).setAlpha(dimLayer, 0.2f);
+        verify(mTransaction).setAlpha(dimLayer, 0.3f);
+        verify(sTestAnimation, times(1)).startAnimation(
+                any(SurfaceControl.class), any(SurfaceControl.Transaction.class),
+                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+    }
+
+    /**
+     * Same as testContainerDimsOpeningAnimationByItself, but this is a more specific case in which
+     * alpha is animated to 0. This corner case is needed to verify that the layer is removed anyway
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
+    public void testContainerDimsClosingAnimationByItself() {
+        mHost.addChild(mChild, 0);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0.2f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0.1f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(mChild, 0f, 0);
+        mDimmer.adjustRelativeLayer(mChild, -1);
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.updateDims(mTransaction);
+        verify(mTransaction).remove(dimLayer);
+    }
+
+    /**
+     * Check the handover of the dim between two windows and the consequent dim animation in between
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
+    public void testMultipleContainersDimmingConsecutively() {
+        TestWindowContainer first = mChild;
+        TestWindowContainer second = new TestWindowContainer(mWm);
+        mHost.addChild(first, 0);
+        mHost.addChild(second, 1);
+
+        mDimmer.adjustAppearance(first, 0.5f, 0);
+        mDimmer.adjustRelativeLayer(first, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.updateDims(mTransaction);
+
+        mDimmer.resetDimStates();
+        mDimmer.adjustAppearance(second, 0.9f, 0);
+        mDimmer.adjustRelativeLayer(second, -1);
+        mDimmer.updateDims(mTransaction);
+
+        verify(sTestAnimation, times(2)).startAnimation(
+                any(SurfaceControl.class), any(SurfaceControl.Transaction.class),
+                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+        verify(mTransaction).setAlpha(dimLayer, 0.5f);
+        verify(mTransaction).setAlpha(dimLayer, 0.9f);
+    }
+
+    /**
+     * Two windows are trying to modify the dim at the same time, but only the last request before
+     * updateDims will be satisfied
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
+    public void testMultipleContainersDimmingAtTheSameTime() {
+        TestWindowContainer first = mChild;
+        TestWindowContainer second = new TestWindowContainer(mWm);
+        mHost.addChild(first, 0);
+        mHost.addChild(second, 1);
+
+        mDimmer.adjustAppearance(first, 0.5f, 0);
+        mDimmer.adjustRelativeLayer(first, -1);
+        SurfaceControl dimLayer = mDimmer.getDimLayer();
+        mDimmer.adjustAppearance(second, 0.9f, 0);
+        mDimmer.adjustRelativeLayer(second, -1);
+        mDimmer.updateDims(mTransaction);
+
+        verify(sTestAnimation, times(1)).startAnimation(
+                any(SurfaceControl.class), any(SurfaceControl.Transaction.class),
+                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+        verify(mTransaction, never()).setAlpha(dimLayer, 0.5f);
+        verify(mTransaction).setAlpha(dimLayer, 0.9f);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
index 8119fd4..3b9ed26 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
@@ -23,15 +23,14 @@
 
 import static org.junit.Assert.assertTrue;
 
-import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
-import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.server.wm.BuildUtils;
 import android.view.Gravity;
 import android.view.IWindow;
 import android.view.SurfaceControl;
@@ -46,12 +45,12 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.server.wm.utils.CommonUtils;
+import com.android.server.wm.utils.TestActivity;
 
 import org.junit.After;
 import org.junit.Before;
@@ -59,11 +58,14 @@
 import org.junit.runner.RunWith;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @Presubmit
 @SmallTest
 @RunWith(WindowTestRunner.class)
 public class SurfaceControlViewHostTests {
+    private static final long WAIT_TIME_S = 5L * BuildUtils.HW_TIMEOUT_MULTIPLIER;
+
     private static final String TAG = "SurfaceControlViewHostTests";
 
     private final ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>(
@@ -76,6 +78,8 @@
     private SurfaceControlViewHost mScvh1;
     private SurfaceControlViewHost mScvh2;
 
+    private SurfaceView mSurfaceView;
+
     @Before
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -96,15 +100,17 @@
         mView1 = new Button(mActivity);
         mView2 = new Button(mActivity);
 
-        mInstrumentation.runOnMainSync(() -> {
-            try {
-                mActivity.attachToSurfaceView(sc);
-            } catch (InterruptedException e) {
-            }
+        CountDownLatch svReadyLatch = new CountDownLatch(1);
+        mActivity.runOnUiThread(() -> addSurfaceView(svReadyLatch));
+        assertTrue("Failed to wait for SV to get created",
+                svReadyLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
+        new SurfaceControl.Transaction().reparent(sc, mSurfaceView.getSurfaceControl())
+                .show(sc).apply();
 
+        mInstrumentation.runOnMainSync(() -> {
             TestWindowlessWindowManager wwm = new TestWindowlessWindowManager(
                     mActivity.getResources().getConfiguration(), sc,
-                    mActivity.mSurfaceView.getHostToken());
+                    mSurfaceView.getHostToken());
 
             mScvh1 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
                     wwm, "requestFocusWithMultipleWindows");
@@ -135,7 +141,7 @@
         }
         assertTrue("Failed to wait for view2", wasVisible);
 
-        IWindow window = IWindow.Stub.asInterface(mActivity.mSurfaceView.getWindowToken());
+        IWindow window = IWindow.Stub.asInterface(mSurfaceView.getWindowToken());
 
         WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window,
                 mScvh1.getInputTransferToken(), true);
@@ -162,43 +168,30 @@
         }
     }
 
-    public static class TestActivity extends Activity implements SurfaceHolder.Callback {
-        private SurfaceView mSurfaceView;
-        private final CountDownLatch mSvReadyLatch = new CountDownLatch(1);
+    private void addSurfaceView(CountDownLatch svReadyLatch) {
+        final FrameLayout content = mActivity.getParentLayout();
+        mSurfaceView = new SurfaceView(mActivity);
+        mSurfaceView.setZOrderOnTop(true);
+        final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(500, 500,
+                Gravity.LEFT | Gravity.TOP);
+        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(@NonNull SurfaceHolder holder) {
+                svReadyLatch.countDown();
+            }
 
-        @Override
-        protected void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            final FrameLayout content = new FrameLayout(this);
-            mSurfaceView = new SurfaceView(this);
-            mSurfaceView.setBackgroundColor(Color.BLACK);
-            mSurfaceView.setZOrderOnTop(true);
-            final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(500, 500,
-                    Gravity.LEFT | Gravity.TOP);
-            content.addView(mSurfaceView, lp);
-            setContentView(content);
-            mSurfaceView.getHolder().addCallback(this);
-        }
+            @Override
+            public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+                    int height) {
 
-        @Override
-        public void surfaceCreated(@NonNull SurfaceHolder holder) {
-            mSvReadyLatch.countDown();
-        }
+            }
 
-        @Override
-        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
-                int height) {
-        }
+            @Override
+            public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
 
-        @Override
-        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
-        }
-
-        public void attachToSurfaceView(SurfaceControl sc) throws InterruptedException {
-            mSvReadyLatch.await();
-            new SurfaceControl.Transaction().reparent(sc, mSurfaceView.getSurfaceControl())
-                    .show(sc).apply();
-        }
+            }
+        });
+        content.addView(mSurfaceView, lp);
     }
 }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
index 3cb4a1d..e65a9fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
@@ -19,6 +19,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
 import android.os.Handler;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.DexmakerShareClassLoaderRule;
 
@@ -39,6 +41,9 @@
     public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule(
             this::onBeforeSystemServicesCreated);
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @WindowTestRunner.MethodWrapperRule
     public final WindowManagerGlobalLockRule mLockRule =
             new WindowManagerGlobalLockRule(mSystemServicesTestRule);
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 b1def8d..51f0404 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -444,7 +444,9 @@
         SurfaceAnimationThread.dispose();
         AnimationThread.dispose();
         UiThread.dispose();
-        mInputChannel.dispose();
+        if (mInputChannel != null) {
+            mInputChannel.dispose();
+        }
 
         tearDownLocalServices();
         // Reset priority booster because animation thread has been changed.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 8a90f12..06f29c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -25,7 +25,9 @@
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE;
 import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
@@ -1759,6 +1761,40 @@
     }
 
     @Test
+    public void testApplyTransaction_createTaskFragmentDecorSurface() {
+        // TODO(b/293654166) remove system organizer requirement once security review is cleared.
+        mController.unregisterOrganizer(mIOrganizer);
+        mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+        final Task task = createTask(mDisplayContent);
+
+        final TaskFragment tf = createTaskFragment(task);
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE).build();
+        mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation);
+
+        assertApplyTransactionAllowed(mTransaction);
+
+        verify(task).moveOrCreateDecorSurfaceFor(tf);
+    }
+
+    @Test
+    public void testApplyTransaction_removeTaskFragmentDecorSurface() {
+        // TODO(b/293654166) remove system organizer requirement once security review is cleared.
+        mController.unregisterOrganizer(mIOrganizer);
+        mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf = createTaskFragment(task);
+
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE).build();
+        mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation);
+
+        assertApplyTransactionAllowed(mTransaction);
+
+        verify(task).removeDecorSurface();
+    }
+
+    @Test
     public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() {
         testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
                 OP_TYPE_REORDER_TO_BOTTOM_OF_TASK);
@@ -1966,7 +2002,8 @@
     /** Setups the mock Task as the parent of the given TaskFragment. */
     private static void setupMockParent(TaskFragment taskFragment, Task mockParent) {
         doReturn(mockParent).when(taskFragment).getTask();
-        doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true, true))
+        doReturn(new TaskFragmentParentInfo(
+                new Configuration(), DEFAULT_DISPLAY, true, true, null /* decorSurface */))
                 .when(mockParent).getTaskFragmentParentInfo();
 
         // Task needs to be visible
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 5e531b4..da7612b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -63,6 +63,7 @@
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 
 import android.app.ActivityManager;
@@ -82,6 +83,7 @@
 import android.util.Xml;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.SurfaceControl;
 import android.window.TaskFragmentOrganizer;
 
 import androidx.test.filters.MediumTest;
@@ -1619,6 +1621,185 @@
         assertFalse(task.isDragResizing());
     }
 
+    @Test
+    public void testMoveOrCreateDecorSurface() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final Task task =  new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+        final ActivityRecord activity = task.getTopMostActivity();
+        final TaskFragment fragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+
+        // Decor surface should not be present initially.
+        assertNull(task.mDecorSurfaceContainer);
+        assertNull(task.getDecorSurface());
+        assertNull(task.getTaskFragmentParentInfo().getDecorSurface());
+
+        // Decor surface should be created.
+        clearInvocations(task);
+        task.moveOrCreateDecorSurfaceFor(fragment);
+
+        assertNotNull(task.mDecorSurfaceContainer);
+        assertNotNull(task.getDecorSurface());
+        verify(task).sendTaskFragmentParentInfoChangedIfNeeded();
+        assertNotNull(task.getTaskFragmentParentInfo().getDecorSurface());
+        assertEquals(fragment, task.mDecorSurfaceContainer.mOwnerTaskFragment);
+
+        // Decor surface should be removed.
+        clearInvocations(task);
+        task.removeDecorSurface();
+
+        assertNull(task.mDecorSurfaceContainer);
+        assertNull(task.getDecorSurface());
+        verify(task).sendTaskFragmentParentInfoChangedIfNeeded();
+        assertNull(task.getTaskFragmentParentInfo().getDecorSurface());
+    }
+
+    @Test
+    public void testMoveOrCreateDecorSurface_whenOwnerTaskFragmentRemoved() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final Task task =  new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+        final ActivityRecord activity = task.getTopMostActivity();
+        final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+
+        task.moveOrCreateDecorSurfaceFor(fragment1);
+
+        assertNotNull(task.mDecorSurfaceContainer);
+        assertNotNull(task.getDecorSurface());
+        assertEquals(fragment1, task.mDecorSurfaceContainer.mOwnerTaskFragment);
+
+        // Transfer ownership
+        task.moveOrCreateDecorSurfaceFor(fragment2);
+
+        assertNotNull(task.mDecorSurfaceContainer);
+        assertNotNull(task.getDecorSurface());
+        assertEquals(fragment2, task.mDecorSurfaceContainer.mOwnerTaskFragment);
+
+        // Safe surface should be removed when the owner TaskFragment is removed.
+        task.removeChild(fragment2);
+
+        verify(task).removeDecorSurface();
+        assertNull(task.mDecorSurfaceContainer);
+        assertNull(task.getDecorSurface());
+    }
+
+    @Test
+    public void testAssignChildLayers_decorSurfacePlacement() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final Task task =  new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+        final ActivityRecord unembeddedActivity = task.getTopMostActivity();
+
+        final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+
+        doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+        spyOn(unembeddedActivity);
+        spyOn(fragment1);
+        spyOn(fragment2);
+
+        // Initially, the decor surface should not be placed.
+        task.assignChildLayers(t);
+        verify(unembeddedActivity).assignLayer(t, 0);
+        verify(fragment1).assignLayer(t, 1);
+        verify(fragment2).assignLayer(t, 2);
+
+        clearInvocations(t);
+        clearInvocations(unembeddedActivity);
+        clearInvocations(fragment1);
+        clearInvocations(fragment2);
+
+        // The decor surface should be placed just above the owner TaskFragment.
+        doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid);
+        doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode();
+        doReturn(true).when(fragment2).isAllowedToBeEmbeddedInTrustedMode();
+        doReturn(true).when(fragment1).isVisible();
+
+        task.moveOrCreateDecorSurfaceFor(fragment1);
+        task.assignChildLayers(t);
+
+        verify(unembeddedActivity).assignLayer(t, 0);
+        verify(fragment1).assignLayer(t, 1);
+        verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2);
+        verify(fragment2).assignLayer(t, 3);
+        verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+        verify(t, never()).setLayer(eq(task.getDecorSurface()), anyInt());
+
+        clearInvocations(t);
+        clearInvocations(unembeddedActivity);
+        clearInvocations(fragment1);
+        clearInvocations(fragment2);
+
+        // The decor surface should be invisible if the owner TaskFragment is invisible.
+        doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid);
+        doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode();
+        doReturn(true).when(fragment2).isAllowedToBeEmbeddedInTrustedMode();
+        doReturn(false).when(fragment1).isVisible();
+
+        task.assignChildLayers(t);
+
+        verify(unembeddedActivity).assignLayer(t, 0);
+        verify(fragment1).assignLayer(t, 1);
+        verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2);
+        verify(fragment2).assignLayer(t, 3);
+        verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, false);
+        verify(t, never()).setLayer(eq(task.getDecorSurface()), anyInt());
+
+        clearInvocations(t);
+        clearInvocations(unembeddedActivity);
+        clearInvocations(fragment1);
+        clearInvocations(fragment2);
+
+        // The decor surface should be placed on below activity from a different UID.
+        doReturn(false).when(unembeddedActivity).isUid(task.effectiveUid);
+        doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode();
+        doReturn(true).when(fragment2).isAllowedToBeEmbeddedInTrustedMode();
+        doReturn(true).when(fragment1).isVisible();
+
+        task.assignChildLayers(t);
+
+        verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 0);
+        verify(unembeddedActivity).assignLayer(t, 1);
+        verify(fragment1).assignLayer(t, 2);
+        verify(fragment2).assignLayer(t, 3);
+        verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+        verify(t, never()).setLayer(eq(task.getDecorSurface()), anyInt());
+
+        clearInvocations(t);
+        clearInvocations(unembeddedActivity);
+        clearInvocations(fragment1);
+        clearInvocations(fragment2);
+
+        // The decor surface should be placed below untrusted embedded TaskFragment.
+        doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid);
+        doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode();
+        doReturn(false).when(fragment2).isAllowedToBeEmbeddedInTrustedMode();
+        doReturn(true).when(fragment1).isVisible();
+
+        task.assignChildLayers(t);
+
+        verify(unembeddedActivity).assignLayer(t, 0);
+        verify(fragment1).assignLayer(t, 1);
+        verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2);
+        verify(fragment2).assignLayer(t, 3);
+        verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+        verify(t, never()).setLayer(eq(task.getDecorSurface()), anyInt());
+
+        clearInvocations(t);
+        clearInvocations(unembeddedActivity);
+        clearInvocations(fragment1);
+        clearInvocations(fragment2);
+
+        // The decor surface should not be placed after removal.
+        task.removeDecorSurface();
+        task.assignChildLayers(t);
+
+        verify(unembeddedActivity).assignLayer(t, 0);
+        verify(fragment1).assignLayer(t, 1);
+        verify(fragment2).assignLayer(t, 2);
+    }
+
     private Task getTestTask() {
         return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index dade3b9..71447e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -2015,6 +2015,9 @@
         transition.collect(leafTaskA);
         rootTaskA.moveToFront("test", leafTaskA);
 
+        // Test has order changes, a shallow check of order changes
+        assertTrue(transition.hasOrderChanges());
+
         // All the tasks were already visible, so there shouldn't be any changes
         ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
                 participants, changes);
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java b/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java
new file mode 100644
index 0000000..c12dcdd
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.os.Bundle;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+/**
+ * TestActivity that will ensure it dismisses keyguard and shows as a fullscreen activity.
+ */
+public class TestActivity extends Activity {
+    private static final int sTypeMask = systemBars() | displayCutout();
+    private FrameLayout mParentLayout;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mParentLayout = new FrameLayout(this);
+        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT);
+        setContentView(mParentLayout, layoutParams);
+
+        WindowInsetsController windowInsetsController = getWindow().getInsetsController();
+        windowInsetsController.hide(sTypeMask);
+        WindowManager.LayoutParams params = getWindow().getAttributes();
+        params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        getWindow().setAttributes(params);
+        getWindow().setDecorFitsSystemWindows(false);
+
+        final KeyguardManager keyguardManager = getInstrumentation().getContext().getSystemService(
+                KeyguardManager.class);
+        if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
+            keyguardManager.requestDismissKeyguard(this, null);
+        }
+    }
+
+    public FrameLayout getParentLayout() {
+        return mParentLayout;
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index bfb159f..dce4818 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -31,6 +31,7 @@
 import static android.app.usage.UsageEvents.Event.LOCUS_ID_SET;
 import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
 import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
+import static android.app.usage.UsageEvents.Event.USER_INTERACTION;
 import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE;
 import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE;
 import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
@@ -42,7 +43,9 @@
 import android.app.usage.EventStats;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
 import android.content.res.Configuration;
+import android.os.PersistableBundle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -575,6 +578,23 @@
                         continue;
                     }
                     break;
+                case USER_INTERACTION:
+                    if (event.mUserInteractionExtrasToken != null) {
+                        String category = packagesTokenData.getString(packageToken,
+                                event.mUserInteractionExtrasToken.mCategoryToken);
+                        String action = packagesTokenData.getString(packageToken,
+                                event.mUserInteractionExtrasToken.mActionToken);
+                        if (TextUtils.isEmpty(category) || TextUtils.isEmpty(action)) {
+                            this.events.remove(i);
+                            dataOmitted = true;
+                            continue;
+                        }
+                        event.mExtras = new PersistableBundle();
+                        event.mExtras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, category);
+                        event.mExtras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, action);
+                        event.mUserInteractionExtrasToken = null;
+                    }
+                    break;
             }
         }
         if (dataOmitted) {
@@ -692,13 +712,30 @@
                                 event.mPackage, event.mLocusId);
                     }
                     break;
+                case USER_INTERACTION:
+                    if (event.mExtras != null && event.mExtras.size() != 0) {
+                        final String category = event.mExtras.getString(
+                                UsageStatsManager.EXTRA_EVENT_CATEGORY);
+                        final String action = event.mExtras.getString(
+                                UsageStatsManager.EXTRA_EVENT_ACTION);
+                        if (!TextUtils.isEmpty(category) && !TextUtils.isEmpty(action)) {
+                            event.mUserInteractionExtrasToken =
+                                    new Event.UserInteractionEventExtrasToken();
+                            event.mUserInteractionExtrasToken.mCategoryToken =
+                                    packagesTokenData.getTokenOrAdd(packageToken, event.mPackage,
+                                            category);
+                            event.mUserInteractionExtrasToken.mActionToken =
+                                    packagesTokenData.getTokenOrAdd(packageToken, event.mPackage,
+                                            action);
+                        }
+                    }
+                    break;
             }
         }
     }
 
     /**
      * Obfuscates the data in this instance of interval stats.
-     *
      * @hide
      */
     public void obfuscateData(PackagesTokenData packagesTokenData) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
index 8138747..d865345 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
@@ -17,8 +17,10 @@
 
 import android.app.usage.ConfigurationStats;
 import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event.UserInteractionEventExtrasToken;
 import android.app.usage.UsageStats;
 import android.content.res.Configuration;
+import android.os.PersistableBundle;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -26,6 +28,8 @@
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -282,6 +286,16 @@
                     event.mLocusIdToken = proto.readInt(
                             EventObfuscatedProto.LOCUS_ID_TOKEN) - 1;
                     break;
+                case (int) EventObfuscatedProto.INTERACTION_EXTRAS:
+                    try {
+                        final long interactionExtrasToken = proto.start(
+                                EventObfuscatedProto.INTERACTION_EXTRAS);
+                        event.mUserInteractionExtrasToken = parseUserInteractionEventExtras(proto);
+                        proto.end(interactionExtrasToken);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to read some user interaction extras from proto.", e);
+                    }
+                    break;
                 case ProtoInputStream.NO_MORE_FIELDS:
                     return event.mPackageToken == PackagesTokenData.UNASSIGNED_TOKEN ? null : event;
             }
@@ -386,7 +400,7 @@
     }
 
     private static void writeEvent(ProtoOutputStream proto, final long statsBeginTime,
-            final UsageEvents.Event event) throws IllegalArgumentException {
+            final UsageEvents.Event event) throws IOException, IllegalArgumentException {
         proto.write(EventObfuscatedProto.PACKAGE_TOKEN, event.mPackageToken + 1);
         if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
             proto.write(EventObfuscatedProto.CLASS_TOKEN, event.mClassToken + 1);
@@ -429,6 +443,12 @@
                             event.mNotificationChannelIdToken + 1);
                 }
                 break;
+            case UsageEvents.Event.USER_INTERACTION:
+                if (event.mUserInteractionExtrasToken != null) {
+                    writeUserInteractionEventExtras(proto, EventObfuscatedProto.INTERACTION_EXTRAS,
+                            event.mUserInteractionExtrasToken);
+                }
+                break;
         }
     }
 
@@ -703,6 +723,9 @@
                 case (int) PendingEventProto.TASK_ROOT_CLASS:
                     event.mTaskRootClass = proto.readString(PendingEventProto.TASK_ROOT_CLASS);
                     break;
+                case (int) PendingEventProto.EXTRAS:
+                    event.mExtras = parsePendingEventExtras(proto, PendingEventProto.EXTRAS);
+                    break;
                 case ProtoInputStream.NO_MORE_FIELDS:
                     // Handle default values for certain events types
                     switch (event.mEventType) {
@@ -757,7 +780,7 @@
     }
 
     private static void writePendingEvent(ProtoOutputStream proto, UsageEvents.Event event)
-            throws IllegalArgumentException {
+            throws IOException, IllegalArgumentException {
         proto.write(PendingEventProto.PACKAGE_NAME, event.mPackage);
         if (event.mClass != null) {
             proto.write(PendingEventProto.CLASS_NAME, event.mClass);
@@ -794,6 +817,11 @@
                             event.mNotificationChannelId);
                 }
                 break;
+            case UsageEvents.Event.USER_INTERACTION:
+                if (event.mExtras != null && event.mExtras.size() != 0) {
+                    writePendingEventExtras(proto, PendingEventProto.EXTRAS, event.mExtras);
+                }
+                break;
         }
     }
 
@@ -888,4 +916,52 @@
             proto.end(token);
         }
     }
+
+    private static UserInteractionEventExtrasToken parseUserInteractionEventExtras(
+            ProtoInputStream proto) throws IOException {
+        UserInteractionEventExtrasToken interactionExtrasToken =
+                new UserInteractionEventExtrasToken();
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) ObfuscatedUserInteractionExtrasProto.CATEGORY_TOKEN:
+                    interactionExtrasToken.mCategoryToken = proto.readInt(
+                            ObfuscatedUserInteractionExtrasProto.CATEGORY_TOKEN) - 1;
+                    break;
+                case (int) ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN:
+                    interactionExtrasToken.mActionToken = proto.readInt(
+                            ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN) - 1;
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    return interactionExtrasToken;
+            }
+        }
+    }
+
+    static void writeUserInteractionEventExtras(ProtoOutputStream proto, long fieldId,
+            UserInteractionEventExtrasToken interactionExtras) {
+        final long token = proto.start(fieldId);
+        proto.write(ObfuscatedUserInteractionExtrasProto.CATEGORY_TOKEN,
+                interactionExtras.mCategoryToken + 1);
+        proto.write(ObfuscatedUserInteractionExtrasProto.ACTION_TOKEN,
+                interactionExtras.mActionToken + 1);
+        proto.end(token);
+    }
+
+    /**
+     * Populates the extra details for pending interaction event from the protobuf stream.
+     */
+    private static PersistableBundle parsePendingEventExtras(ProtoInputStream proto, long fieldId)
+            throws IOException {
+        return PersistableBundle.readFromStream(new ByteArrayInputStream(proto.readBytes(fieldId)));
+    }
+
+    /**
+     * Write the extra details for pending interaction event to a protobuf stream.
+     */
+    static void writePendingEventExtras(ProtoOutputStream proto, long fieldId,
+            PersistableBundle eventExtras) throws IOException {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        eventExtras.writeToStream(baos);
+        proto.write(fieldId, baos.toByteArray());
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4c56f33..0e1e0c8 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -83,6 +83,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -91,6 +92,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -193,6 +195,11 @@
 
     private static final char TOKEN_DELIMITER = '/';
 
+    // The maximum length for extras {@link UsageStatsManager#EXTRA_EVENT_CATEGORY},
+    // {@link UsageStatsManager#EXTRA_EVENT_ACTION} in a {@link UsageEvents.Event#mExtras}.
+    // The value will be truncated at this limit.
+    private static final int MAX_TEXT_LENGTH = 127;
+
     // Handler message types.
     static final int MSG_REPORT_EVENT = 0;
     static final int MSG_FLUSH_TO_DISK = 1;
@@ -1814,6 +1821,13 @@
         mHandler.removeMessages(MSG_FLUSH_TO_DISK);
     }
 
+    private String getTrimmedString(String input) {
+        if (input != null && input.length() > MAX_TEXT_LENGTH) {
+            return input.substring(0, MAX_TEXT_LENGTH);
+        }
+        return input;
+    }
+
     /**
      * Called by the Binder stub.
      */
@@ -2253,6 +2267,32 @@
             }
         }
 
+        private void reportUserInteractionInnerHelper(String packageName, @UserIdInt int userId,
+                PersistableBundle extras) {
+            if (Flags.reportUsageStatsPermission()) {
+                if (!canReportUsageStats()) {
+                    throw new SecurityException(
+                        "Only the system or holders of the REPORT_USAGE_STATS"
+                            + " permission are allowed to call reportUserInteraction");
+                }
+            } else {
+                if (!isCallingUidSystem()) {
+                    throw new SecurityException("Only system is allowed to call"
+                        + " reportUserInteraction");
+                }
+            }
+
+            // Verify if this package exists before reporting an event for it.
+            if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) {
+                throw new IllegalArgumentException("Package " + packageName + "not exist!");
+            }
+
+            final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime());
+            event.mPackage = packageName;
+            event.mExtras = extras;
+            reportEventOrAddToQueue(userId, event);
+        }
+
         @Override
         public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime,
                 long endTime, String callingPackage, int userId) {
@@ -2686,23 +2726,36 @@
 
         @Override
         public void reportUserInteraction(String packageName, int userId) {
+            reportUserInteractionInnerHelper(packageName, userId, null);
+        }
+
+        @Override
+        public void reportUserInteractionWithBundle(String packageName, @UserIdInt int userId,
+                PersistableBundle extras) {
             Objects.requireNonNull(packageName);
-            if (Flags.reportUsageStatsPermission()) {
-                if (!canReportUsageStats()) {
-                    throw new SecurityException(
-                        "Only the system or holders of the REPORT_USAGE_STATS"
-                            + " permission are allowed to call reportUserInteraction");
-                }
-            } else {
-                if (!isCallingUidSystem()) {
-                    throw new SecurityException("Only system is allowed to call"
-                            + " reportUserInteraction");
-                }
+            if (extras == null || extras.size() == 0) {
+                throw new IllegalArgumentException("Emtry extras!");
             }
 
-            final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime());
-            event.mPackage = packageName;
-            reportEventOrAddToQueue(userId, event);
+            // Only category/action are allowed now, other unknown keys will be trimmed.
+            // Also, empty category/action is not meanful.
+            String category = extras.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY);
+            if (TextUtils.isEmpty(category)) {
+                throw new IllegalArgumentException("Empty "
+                        + UsageStatsManager.EXTRA_EVENT_CATEGORY);
+            }
+            String action = extras.getString(UsageStatsManager.EXTRA_EVENT_ACTION);
+            if (TextUtils.isEmpty(action)) {
+                throw new IllegalArgumentException("Empty "
+                        + UsageStatsManager.EXTRA_EVENT_ACTION);
+            }
+
+            PersistableBundle extrasCopy = new PersistableBundle();
+            extrasCopy.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY,
+                    getTrimmedString(category));
+            extrasCopy.putString(UsageStatsManager.EXTRA_EVENT_ACTION, getTrimmedString(action));
+
+            reportUserInteractionInnerHelper(packageName, userId, extrasCopy);
         }
 
         @Override
@@ -3160,6 +3213,24 @@
         }
 
         @Override
+        public void reportUserInteractionEvent(@NonNull String pkgName, @UserIdInt int userId,
+                @NonNull PersistableBundle extras) {
+            if (extras != null && extras.size() != 0) {
+                // Truncate the value if necessary.
+                String category = extras.getString(UsageStatsManager.EXTRA_EVENT_CATEGORY);
+                String action = extras.getString(UsageStatsManager.EXTRA_EVENT_ACTION);
+                extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY,
+                        getTrimmedString(category));
+                extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, getTrimmedString(action));
+            }
+
+            Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime());
+            event.mPackage = pkgName;
+            event.mExtras = extras;
+            reportEventOrAddToQueue(userId, event);
+        }
+
+        @Override
         public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
             return mAppStandby.isAppIdleFiltered(packageName, uidForAppId,
                     userId, SystemClock.elapsedRealtime());
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 9b67ab6..3bc7752 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -1110,6 +1110,10 @@
         if (event.mNotificationChannelId != null) {
             pw.printPair("channelId", event.mNotificationChannelId);
         }
+
+        if ((event.mEventType == Event.USER_INTERACTION) && (event.mExtras != null)) {
+            pw.print(event.mExtras.toString());
+        }
         pw.printHexPair("flags", event.mFlags);
         pw.println();
     }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 356e1fa..8354d98 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -39,5 +39,5 @@
 private fun ClassNodes.isAidlClass(className: String): Boolean {
     return hasClass(className) &&
             hasClass("$className\$Stub") &&
-            hasClass("$className\$Proxy")
+            hasClass("$className\$Stub\$Proxy")
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 214de59..3956893 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -223,16 +223,16 @@
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD]
     )
-## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class
   Compiled from "IPretendingAidl.java"
-public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy
   minor version: 0
   major version: 61
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 3
-  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy();
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
     Code:
@@ -243,7 +243,7 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy;
 
   public static int addTwo(int);
     descriptor: (I)I
@@ -262,7 +262,8 @@
 SourceFile: "IPretendingAidl.java"
 NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
 InnerClasses:
-  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;          // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 ## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
   Compiled from "IPretendingAidl.java"
 public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
@@ -303,6 +304,7 @@
 NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
 InnerClasses:
   public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 ## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
   Compiled from "IPretendingAidl.java"
 public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
@@ -315,11 +317,11 @@
 }
 SourceFile: "IPretendingAidl.java"
 NestMembers:
-  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
 InnerClasses:
-  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 9031228..9349355 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -1,13 +1,13 @@
-## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class
   Compiled from "IPretendingAidl.java"
-public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy
   minor version: 0
   major version: 61
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy();
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
     Code:
@@ -30,7 +30,8 @@
          x: athrow
 }
 InnerClasses:
-  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -71,6 +72,7 @@
 }
 InnerClasses:
   public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -89,8 +91,8 @@
   interfaces: 0, fields: 0, methods: 0, attributes: 4
 }
 InnerClasses:
-  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -98,8 +100,8 @@
   x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestMembers:
-  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index e01f49b..4f8c408e 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -205,16 +205,16 @@
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
-## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class
   Compiled from "IPretendingAidl.java"
-public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy
   minor version: 0
   major version: 61
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy();
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
     Code:
@@ -225,7 +225,7 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy;
 
   public static int addTwo(int);
     descriptor: (I)I
@@ -242,7 +242,8 @@
             0       4     0     a   I
 }
 InnerClasses:
-  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -288,6 +289,7 @@
 }
 InnerClasses:
   public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -306,8 +308,8 @@
   interfaces: 0, fields: 0, methods: 0, attributes: 4
 }
 InnerClasses:
-  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -315,8 +317,8 @@
   x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestMembers:
-  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 9031228..9349355 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -1,13 +1,13 @@
-## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class
   Compiled from "IPretendingAidl.java"
-public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy
   minor version: 0
   major version: 61
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy();
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
     Code:
@@ -30,7 +30,8 @@
          x: athrow
 }
 InnerClasses:
-  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -71,6 +72,7 @@
 }
 InnerClasses:
   public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -89,8 +91,8 @@
   interfaces: 0, fields: 0, methods: 0, attributes: 4
 }
 InnerClasses:
-  public static #x= #x of #x;            // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -98,8 +100,8 @@
   x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestMembers:
-  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index 5246355..5ff3cde 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -289,13 +289,13 @@
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
-## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy.class
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class
   Compiled from "IPretendingAidl.java"
-public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy
   minor version: 0
   major version: 61
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 3, attributes: 4
   private static {};
@@ -303,17 +303,17 @@
     flags: (0x000a) ACC_PRIVATE, ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
-  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Proxy();
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
          x: ldc           #x                 // String <init>
          x: ldc           #x                 // String ()V
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -324,14 +324,14 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy;
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy;
 
   public static int addTwo(int);
     descriptor: (I)I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
          x: ldc           #x                 // String addTwo
          x: ldc           #x                 // String (I)I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -346,7 +346,8 @@
            11       4     0     a   I
 }
 InnerClasses:
-  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;          // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -412,6 +413,7 @@
 }
 InnerClasses:
   public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -439,8 +441,8 @@
          x: return
 }
 InnerClasses:
-  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
   public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;          // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
@@ -448,8 +450,8 @@
   x: #x()
     com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
 NestMembers:
-  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Proxy
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
   Compiled from "TinyFrameworkCallerCheck.java"
 class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java
index 583e13c..0a07c2b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java
@@ -25,11 +25,12 @@
         public static int addOne(int a) {
             return a + 1;
         }
-    }
 
-    public static class Proxy {
-        public static int addTwo(int a) {
-            return a + 2;
+        public static class Proxy {
+            public static int addTwo(int a) {
+                return a + 2;
+            }
         }
     }
+
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 0d52791..d350105 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -289,6 +289,6 @@
     @Test
     public void testAidlHeuristics() {
         assertThat(IPretendingAidl.Stub.addOne(1)).isEqualTo(2);
-        assertThat(IPretendingAidl.Proxy.addTwo(1)).isEqualTo(3);
+        assertThat(IPretendingAidl.Stub.Proxy.addTwo(1)).isEqualTo(3);
     }
 }