Merge "Revert "Revert "Created HardwareBufferRenderer to support rendering into..."""
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index d7163d8..5599e54 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -1933,8 +1933,9 @@
                     Intent.FLAG_RECEIVER_REGISTERED_ONLY
                             | Intent.FLAG_RECEIVER_FOREGROUND
                             | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-            mTimeTickOptions = BroadcastOptions
-                    .makeRemovingMatchingFilter(new IntentFilter(Intent.ACTION_TIME_TICK))
+            mTimeTickOptions = BroadcastOptions.makeBasic()
+                    .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                    .setDeferUntilActive(true)
                     .toBundle();
             mTimeTickTrigger = new IAlarmListener.Stub() {
                 @Override
@@ -4252,8 +4253,8 @@
                     }
                 }
                 // And send a TIME_TICK right now, since it is important to get the UI updated.
-                mHandler.post(() ->
-                        getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL));
+                mHandler.post(() -> getContext().sendBroadcastAsUser(mTimeTickIntent,
+                        UserHandle.ALL, null, mTimeTickOptions));
             } else {
                 mNonInteractiveStartTime = nowELAPSED;
             }
diff --git a/core/api/current.txt b/core/api/current.txt
index bca9913..8d27311 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3538,6 +3538,7 @@
     method public float getCenterY();
     method public int getMode();
     method public float getScale();
+    method public boolean isActivated();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.MagnificationConfig> CREATOR;
     field public static final int MAGNIFICATION_MODE_DEFAULT = 0; // 0x0
@@ -3548,6 +3549,7 @@
   public static final class MagnificationConfig.Builder {
     ctor public MagnificationConfig.Builder();
     method @NonNull public android.accessibilityservice.MagnificationConfig build();
+    method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setActivated(boolean);
     method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterX(float);
     method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterY(float);
     method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setMode(int);
@@ -10851,6 +10853,7 @@
     field public static final String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
     field public static final String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
     field public static final String EXTRA_CHOOSER_CUSTOM_ACTIONS = "android.intent.extra.CHOOSER_CUSTOM_ACTIONS";
+    field public static final String EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION = "android.intent.extra.CHOOSER_PAYLOAD_RESELECTION_ACTION";
     field public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
     field public static final String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
     field public static final String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
@@ -19341,7 +19344,9 @@
 
   public final class DisplayManager {
     method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int);
+    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int);
     method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
+    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback);
     method public android.view.Display getDisplay(int);
     method public android.view.Display[] getDisplays();
     method public android.view.Display[] getDisplays(String);
@@ -24578,8 +24583,8 @@
 
   public final class RouteListingPreference implements android.os.Parcelable {
     method public int describeContents();
-    method @Nullable public android.content.ComponentName getInAppOnlyItemRoutingReceiver();
     method @NonNull public java.util.List<android.media.RouteListingPreference.Item> getItems();
+    method @Nullable public android.content.ComponentName getLinkedItemComponentName();
     method public boolean getUseSystemOrdering();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
@@ -24590,37 +24595,39 @@
   public static final class RouteListingPreference.Builder {
     ctor public RouteListingPreference.Builder();
     method @NonNull public android.media.RouteListingPreference build();
-    method @NonNull public android.media.RouteListingPreference.Builder setInAppOnlyItemRoutingReceiver(@Nullable android.content.ComponentName);
     method @NonNull public android.media.RouteListingPreference.Builder setItems(@NonNull java.util.List<android.media.RouteListingPreference.Item>);
+    method @NonNull public android.media.RouteListingPreference.Builder setLinkedItemComponentName(@Nullable android.content.ComponentName);
     method @NonNull public android.media.RouteListingPreference.Builder setUseSystemOrdering(boolean);
   }
 
   public static final class RouteListingPreference.Item implements android.os.Parcelable {
     method public int describeContents();
-    method @Nullable public CharSequence getCustomDisableReasonMessage();
-    method public int getDisableReason();
+    method @Nullable public CharSequence getCustomSubtextMessage();
     method public int getFlags();
     method @NonNull public String getRouteId();
-    method public int getSessionParticipantCount();
+    method public int getSelectionBehavior();
+    method public int getSubText();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
-    field public static final int DISABLE_REASON_AD = 3; // 0x3
-    field public static final int DISABLE_REASON_CUSTOM = 5; // 0x5
-    field public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2; // 0x2
-    field public static final int DISABLE_REASON_IN_APP_ONLY = 4; // 0x4
-    field public static final int DISABLE_REASON_NONE = 0; // 0x0
-    field public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1; // 0x1
     field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
     field public static final int FLAG_SUGGESTED_ROUTE = 2; // 0x2
+    field public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2; // 0x2
+    field public static final int SELECTION_BEHAVIOR_NONE = 0; // 0x0
+    field public static final int SELECTION_BEHAVIOR_TRANSFER = 1; // 0x1
+    field public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 3; // 0x3
+    field public static final int SUBTEXT_CUSTOM = 10000; // 0x2710
+    field public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 2; // 0x2
+    field public static final int SUBTEXT_NONE = 0; // 0x0
+    field public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 1; // 0x1
   }
 
   public static final class RouteListingPreference.Item.Builder {
     ctor public RouteListingPreference.Item.Builder(@NonNull String);
     method @NonNull public android.media.RouteListingPreference.Item build();
-    method @NonNull public android.media.RouteListingPreference.Item.Builder setCustomDisableReasonMessage(@Nullable CharSequence);
-    method @NonNull public android.media.RouteListingPreference.Item.Builder setDisableReason(int);
+    method @NonNull public android.media.RouteListingPreference.Item.Builder setCustomSubtextMessage(@Nullable CharSequence);
     method @NonNull public android.media.RouteListingPreference.Item.Builder setFlags(int);
-    method @NonNull public android.media.RouteListingPreference.Item.Builder setSessionParticipantCount(@IntRange(from=0) int);
+    method @NonNull public android.media.RouteListingPreference.Item.Builder setSelectionBehavior(int);
+    method @NonNull public android.media.RouteListingPreference.Item.Builder setSubText(int);
   }
 
   public final class RoutingSessionInfo implements android.os.Parcelable {
@@ -51356,8 +51363,12 @@
     method public android.view.SurfaceControl getSurfaceControl();
     method public void setChildSurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
     method public void setSecure(boolean);
+    method public void setSurfaceLifecycle(int);
     method public void setZOrderMediaOverlay(boolean);
     method public void setZOrderOnTop(boolean);
+    field public static final int SURFACE_LIFECYCLE_DEFAULT = 0; // 0x0
+    field public static final int SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT = 2; // 0x2
+    field public static final int SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY = 1; // 0x1
   }
 
   public class TextureView extends android.view.View {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f5653516..fc7b9d4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -8911,7 +8911,6 @@
   }
 
   public class IptvFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
-    ctor public IptvFrontendSettings(@NonNull byte[], @NonNull byte[], int, int, @NonNull android.media.tv.tuner.frontend.IptvFrontendSettingsFec, int, int, long, @NonNull String);
     method @NonNull public static android.media.tv.tuner.frontend.IptvFrontendSettings.Builder builder();
     method @IntRange(from=0) public long getBitrate();
     method @NonNull public String getContentUrl();
@@ -8946,7 +8945,6 @@
   }
 
   public class IptvFrontendSettingsFec {
-    ctor public IptvFrontendSettingsFec(int, int, int);
     method @NonNull public static android.media.tv.tuner.frontend.IptvFrontendSettingsFec.Builder builder();
     method @IntRange(from=0) public int getFecColNum();
     method @IntRange(from=0) public int getFecRowNum();
@@ -11910,9 +11908,10 @@
     method public int onSwitchToSubscriptionWithPort(int, int, @Nullable String, boolean);
     method public abstract int onUpdateSubscriptionNickname(int, String, String);
     field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE";
+    field @RequiresPermission(android.Manifest.permission.BIND_EUICC_SERVICE) public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
     field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
-    field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
-    field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+    field @RequiresPermission(android.Manifest.permission.BIND_EUICC_SERVICE) public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
+    field @RequiresPermission(android.Manifest.permission.BIND_EUICC_SERVICE) public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
     field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
     field @Deprecated public static final String ACTION_RESOLVE_CONFIRMATION_CODE = "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
     field public static final String ACTION_RESOLVE_DEACTIVATE_SIM = "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM";
@@ -11921,6 +11920,7 @@
     field public static final String ACTION_START_CARRIER_ACTIVATION = "android.service.euicc.action.START_CARRIER_ACTIVATION";
     field public static final String ACTION_START_EUICC_ACTIVATION = "android.service.euicc.action.START_EUICC_ACTIVATION";
     field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+    field @RequiresPermission(android.Manifest.permission.BIND_EUICC_SERVICE) public static final String ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.TRANSFER_EMBEDDED_SUBSCRIPTIONS";
     field public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI";
     field public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService";
     field public static final String EXTRA_RESOLUTION_ALLOW_POLICY_RULES = "android.service.euicc.extra.RESOLUTION_ALLOW_POLICY_RULES";
@@ -14345,7 +14345,6 @@
     field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
     field public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; // 0x3
     field public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 1; // 0x1
-    field public static final int ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS = 4; // 0x4
     field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2
     field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
     field public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5; // 0x5
@@ -14875,11 +14874,13 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isSupportedCountry(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setSupportedCountries(@NonNull java.util.List<java.lang.String>);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setUnsupportedCountries(@NonNull java.util.List<java.lang.String>);
+    field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
     field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
     field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED";
     field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
     field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
     field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+    field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final String ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.TRANSFER_EMBEDDED_SUBSCRIPTIONS";
     field public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4; // 0x4
     field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2
     field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5f2f623..7a460ee 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -464,8 +464,11 @@
 
   public class WallpaperManager {
     method @Nullable public android.graphics.Bitmap getBitmap();
+    method @Nullable public android.graphics.Bitmap getBitmapAsUser(int, boolean, int);
+    method public boolean isLockscreenLiveWallpaperEnabled();
     method @Nullable public android.graphics.Rect peekBitmapDimensions();
     method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
+    method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float);
     method public boolean shouldEnableWideColorGamut();
     method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
   }
@@ -1869,7 +1872,10 @@
   public class Build {
     method public static boolean is64BitAbi(String);
     method public static boolean isDebuggable();
+    field @Nullable public static final String BRAND_FOR_ATTESTATION;
     field public static final boolean IS_EMULATOR;
+    field @Nullable public static final String MODEL_FOR_ATTESTATION;
+    field @Nullable public static final String PRODUCT_FOR_ATTESTATION;
   }
 
   public static class Build.VERSION {
@@ -2067,6 +2073,7 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
+    method public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported();
     method public boolean isVisibleBackgroundUsersSupported();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
   }
diff --git a/core/java/android/accessibilityservice/MagnificationConfig.java b/core/java/android/accessibilityservice/MagnificationConfig.java
index ddafb22..486dc50 100644
--- a/core/java/android/accessibilityservice/MagnificationConfig.java
+++ b/core/java/android/accessibilityservice/MagnificationConfig.java
@@ -76,6 +76,7 @@
     }
 
     private int mMode = MAGNIFICATION_MODE_DEFAULT;
+    private boolean mActivated = false;
     private float mScale = Float.NaN;
     private float mCenterX = Float.NaN;
     private float mCenterY = Float.NaN;
@@ -86,6 +87,7 @@
 
     private MagnificationConfig(@NonNull Parcel parcel) {
         mMode = parcel.readInt();
+        mActivated = parcel.readBoolean();
         mScale = parcel.readFloat();
         mCenterX = parcel.readFloat();
         mCenterY = parcel.readFloat();
@@ -102,9 +104,20 @@
     }
 
     /**
+     * Returns the activated state of the controlling magnifier. The controlling magnifier can be
+     * activated even if the scale returned by {@link MagnificationConfig#getScale()} equals to 1.0.
+     *
+     * @return {@code true} if the magnifier is showing on screen,
+     *         {@code false} otherwise.
+     */
+    public boolean isActivated() {
+        return mActivated;
+    }
+
+    /**
      * Returns the magnification scale of the controlling magnifier
      *
-     * @return the scale If the controlling magnifier is not activated, it returns 1 by default
+     * @return The magnification scale
      */
     public float getScale() {
         return mScale;
@@ -113,9 +126,7 @@
     /**
      * Returns the screen-relative X coordinate of the center of the magnification viewport.
      *
-     * @return the X coordinate. If the controlling magnifier is {@link #MAGNIFICATION_MODE_WINDOW}
-     * but not enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
-     * #MAGNIFICATION_MODE_FULLSCREEN} but not enabled, it returns 0
+     * @return The X coordinate
      */
     public float getCenterX() {
         return mCenterX;
@@ -124,9 +135,7 @@
     /**
      * Returns the screen-relative Y coordinate of the center of the magnification viewport.
      *
-     * @return the Y coordinate If the controlling magnifier is {@link #MAGNIFICATION_MODE_WINDOW}
-     * but not enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
-     * #MAGNIFICATION_MODE_FULLSCREEN} but not enabled, it returns 0
+     * @return The Y coordinate
      */
     public float getCenterY() {
         return mCenterY;
@@ -138,6 +147,8 @@
         StringBuilder stringBuilder = new StringBuilder("MagnificationConfig[");
         stringBuilder.append("mode: ").append(getMode());
         stringBuilder.append(", ");
+        stringBuilder.append("activated: ").append(isActivated());
+        stringBuilder.append(", ");
         stringBuilder.append("scale: ").append(getScale());
         stringBuilder.append(", ");
         stringBuilder.append("centerX: ").append(getCenterX());
@@ -155,6 +166,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
         parcel.writeInt(mMode);
+        parcel.writeBoolean(mActivated);
         parcel.writeFloat(mScale);
         parcel.writeFloat(mCenterX);
         parcel.writeFloat(mCenterY);
@@ -166,6 +178,7 @@
     public static final class Builder {
 
         private int mMode = MAGNIFICATION_MODE_DEFAULT;
+        private boolean mActivated = true;
         private float mScale = Float.NaN;
         private float mCenterX = Float.NaN;
         private float mCenterY = Float.NaN;
@@ -189,6 +202,18 @@
         }
 
         /**
+         * Sets magnification activated state.
+         *
+         * @param activated The magnification activated state
+         * @return This builder
+         */
+        @NonNull
+        public MagnificationConfig.Builder setActivated(boolean activated) {
+            mActivated = activated;
+            return this;
+        }
+
+        /**
          * Sets the magnification scale.
          *
          * @param scale The magnification scale, in the range [1, 8]
@@ -237,6 +262,7 @@
         public MagnificationConfig build() {
             MagnificationConfig magnificationConfig = new MagnificationConfig();
             magnificationConfig.mMode = mMode;
+            magnificationConfig.mActivated = mActivated;
             magnificationConfig.mScale = mScale;
             magnificationConfig.mCenterX = mCenterX;
             magnificationConfig.mCenterY = mCenterY;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index f9e4081..73f34eb 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -42,7 +42,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.database.DatabaseUtils;
-import android.healthconnect.HealthConnectManager;
+import android.health.connect.HealthConnectManager;
 import android.media.AudioAttributes.AttributeUsage;
 import android.os.Binder;
 import android.os.Build;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 9b348fc..1345910 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3885,8 +3885,11 @@
                 }
 
                 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
-                    ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
-                    mPersonList.addAll(people);
+                    ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST,
+                            android.app.Person.class);
+                    if (people != null && !people.isEmpty()) {
+                        mPersonList.addAll(people);
+                    }
                 }
 
                 if (mN.getSmallIcon() == null && mN.icon != 0) {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 52d1d68..031d351 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -116,7 +116,7 @@
 import android.hardware.radio.RadioManager;
 import android.hardware.usb.IUsbManager;
 import android.hardware.usb.UsbManager;
-import android.healthconnect.HealthServicesInitializer;
+import android.health.connect.HealthServicesInitializer;
 import android.location.CountryDetector;
 import android.location.ICountryDetector;
 import android.location.ILocationManager;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index f3a83d8..a2cacc5 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -784,6 +784,7 @@
      * @return true if the lockscreen wallpaper always uses a wallpaperService, not a static image
      * @hide
      */
+    @TestApi
     public boolean isLockscreenLiveWallpaperEnabled() {
         return mLockscreenLiveWallpaper;
     }
@@ -1258,6 +1259,8 @@
      * @param which Specifies home or lock screen
      * @hide
      */
+    @TestApi
+    @Nullable
     public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
         return sGlobals.peekWallpaperBitmap(mContext, true, which, userId, hardware, cmProxy);
@@ -2421,6 +2424,7 @@
      *
      * @hide
      */
+    @TestApi
     public void setWallpaperZoomOut(@NonNull IBinder windowToken, float zoom) {
         if (zoom < 0 || zoom > 1f) {
             throw new IllegalArgumentException("zoom must be between 0 and 1: " + zoom);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6e6d4e3..d8ec7cc 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3961,6 +3961,7 @@
      *
      * @throws SecurityException if caller is not device owner or profile owner of org-owned device
      *     or if called on a parent instance
+     * @throws UnsupportedOperationException if the device does not support MTE
      * @param policy the MTE policy to be set
      */
     public void setMtePolicy(@MtePolicy int policy) {
diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java
index b647fc3..4dee159 100644
--- a/core/java/android/app/time/TimeZoneCapabilities.java
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -57,6 +57,16 @@
      */
     @NonNull private final UserHandle mUserHandle;
     private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
+
+    /**
+     * The values of the user's "Use location" value, AKA the Master Location Switch.
+     *
+     * <p>This is only exposed for SettingsUI and so is not part of the SDK API.
+     *
+     * <p>This is not treated as a CapabilityState as it's a boolean value that all user's have.
+     */
+    private final boolean mUseLocationEnabled;
+
     private final @CapabilityState int mConfigureGeoDetectionEnabledCapability;
     private final @CapabilityState int mSetManualTimeZoneCapability;
 
@@ -64,6 +74,7 @@
         this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
         this.mConfigureAutoDetectionEnabledCapability =
                 builder.mConfigureAutoDetectionEnabledCapability;
+        this.mUseLocationEnabled = builder.mUseLocationEnabled;
         this.mConfigureGeoDetectionEnabledCapability =
                 builder.mConfigureGeoDetectionEnabledCapability;
         this.mSetManualTimeZoneCapability = builder.mSetManualTimeZoneCapability;
@@ -74,6 +85,7 @@
         UserHandle userHandle = UserHandle.readFromParcel(in);
         return new TimeZoneCapabilities.Builder(userHandle)
                 .setConfigureAutoDetectionEnabledCapability(in.readInt())
+                .setUseLocationEnabled(in.readBoolean())
                 .setConfigureGeoDetectionEnabledCapability(in.readInt())
                 .setSetManualTimeZoneCapability(in.readInt())
                 .build();
@@ -83,6 +95,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         UserHandle.writeToParcel(mUserHandle, dest);
         dest.writeInt(mConfigureAutoDetectionEnabledCapability);
+        dest.writeBoolean(mUseLocationEnabled);
         dest.writeInt(mConfigureGeoDetectionEnabledCapability);
         dest.writeInt(mSetManualTimeZoneCapability);
     }
@@ -98,6 +111,20 @@
     }
 
     /**
+     * Returns {@code true} if the device's location can be used by the Android system, and
+     * therefore the platform components running on behalf of the user. At the time of writing, the
+     * user can change this via the "Use location" setting on the Location settings screen.
+     *
+     * Not part of the SDK API because it is intended for use by SettingsUI, which can display
+     * text about needing it to be on for location-based time zone detection.
+     * @hide
+     *
+     */
+    public boolean isUseLocationEnabled() {
+        return mUseLocationEnabled;
+    }
+
+    /**
      * Returns the capability state associated with the user's ability to modify the geolocation
      * detection setting. The setting can be updated via {@link
      * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
@@ -167,6 +194,7 @@
         return mUserHandle.equals(that.mUserHandle)
                 && mConfigureAutoDetectionEnabledCapability
                 == that.mConfigureAutoDetectionEnabledCapability
+                && mUseLocationEnabled == that.mUseLocationEnabled
                 && mConfigureGeoDetectionEnabledCapability
                 == that.mConfigureGeoDetectionEnabledCapability
                 && mSetManualTimeZoneCapability == that.mSetManualTimeZoneCapability;
@@ -184,6 +212,7 @@
                 + "mUserHandle=" + mUserHandle
                 + ", mConfigureAutoDetectionEnabledCapability="
                 + mConfigureAutoDetectionEnabledCapability
+                + ", mUseLocationEnabled=" + mUseLocationEnabled
                 + ", mConfigureGeoDetectionEnabledCapability="
                 + mConfigureGeoDetectionEnabledCapability
                 + ", mSetManualTimeZoneCapability=" + mSetManualTimeZoneCapability
@@ -199,6 +228,7 @@
 
         @NonNull private UserHandle mUserHandle;
         private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
+        private Boolean mUseLocationEnabled;
         private @CapabilityState int mConfigureGeoDetectionEnabledCapability;
         private @CapabilityState int mSetManualTimeZoneCapability;
 
@@ -211,6 +241,7 @@
             mUserHandle = capabilitiesToCopy.mUserHandle;
             mConfigureAutoDetectionEnabledCapability =
                 capabilitiesToCopy.mConfigureAutoDetectionEnabledCapability;
+            mUseLocationEnabled = capabilitiesToCopy.mUseLocationEnabled;
             mConfigureGeoDetectionEnabledCapability =
                 capabilitiesToCopy.mConfigureGeoDetectionEnabledCapability;
             mSetManualTimeZoneCapability =
@@ -223,6 +254,12 @@
             return this;
         }
 
+        /** Sets the values for "use location". See {@link #isUseLocationEnabled()}. */
+        public Builder setUseLocationEnabled(boolean useLocation) {
+            mUseLocationEnabled = useLocation;
+            return this;
+        }
+
         /**
          * Sets the value for the "configure geolocation time zone detection enabled" capability.
          */
@@ -242,6 +279,7 @@
         public TimeZoneCapabilities build() {
             verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability,
                     "configureAutoDetectionEnabledCapability");
+            Objects.requireNonNull(mUseLocationEnabled, "useLocationEnabled");
             verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability,
                     "configureGeoDetectionEnabledCapability");
             verifyCapabilitySet(mSetManualTimeZoneCapability,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5f1502f..387e8ae 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6151,10 +6151,10 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
-     * {@link android.healthconnect.HealthConnectManager}.
+     * {@link android.health.connect.HealthConnectManager}.
      *
      * @see #getSystemService(String)
-     * @see android.healthconnect.HealthConnectManager
+     * @see android.health.connect.HealthConnectManager
      */
     public static final String HEALTHCONNECT_SERVICE = "healthconnect";
 
@@ -6198,6 +6198,16 @@
     public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.telephony.satellite.SatelliteManager} for accessing satellite functionality.
+     *
+     * @see #getSystemService(String)
+     * @see android.telephony.satellite.SatelliteManager
+     * @hide
+     */
+    public static final String SATELLITE_SERVICE = "satellite";
+
+    /**
      * 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 5714032..30984b2 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5829,7 +5829,6 @@
      * in the sharesheet.
      * A reselection action allows the user to return to the source app to change the content being
      * shared.
-     * @hide
      */
     public static final String EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION =
             "android.intent.extra.CHOOSER_PAYLOAD_RESELECTION_ACTION";
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 115d4b0..e3016a4 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -65,6 +65,8 @@
 
     void bypassNextAllowedApexUpdateCheck(boolean value);
 
+    void disableVerificationForUid(int uid);
+
     void setAllowUnlimitedSilentUpdates(String installerPackageName);
     void setSilentUpdatesThrottleTime(long throttleTimeInSeconds);
     void checkInstallConstraints(String installerPackageName, in List<String> packageNames,
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 08238ca..d13a97d 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -66,6 +66,7 @@
 public final class DisplayManager {
     private static final String TAG = "DisplayManager";
     private static final boolean DEBUG = false;
+    private static final boolean ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE = false;
 
     private final Context mContext;
     private final DisplayManagerGlobal mGlobal;
@@ -938,6 +939,24 @@
 
     /**
      * Creates a virtual display.
+     *
+     * @see #createVirtualDisplay(String, int, int, int, float, Surface, int,
+     * Handler, VirtualDisplay.Callback)
+     */
+    @Nullable
+    public VirtualDisplay createVirtualDisplay(@NonNull String name,
+            @IntRange(from = 1) int width,
+            @IntRange(from = 1) int height,
+            @IntRange(from = 1) int densityDpi,
+            float requestedRefreshRate,
+            @Nullable Surface surface,
+            @VirtualDisplayFlag int flags) {
+        return createVirtualDisplay(name, width, height, densityDpi, requestedRefreshRate,
+                surface, flags, null, null);
+    }
+
+    /**
+     * Creates a virtual display.
      * <p>
      * The content of a virtual display is rendered to a {@link Surface} provided
      * by the application.
@@ -987,12 +1006,82 @@
             @VirtualDisplayFlag int flags,
             @Nullable VirtualDisplay.Callback callback,
             @Nullable Handler handler) {
+        return createVirtualDisplay(name, width, height, densityDpi, 0.0f, surface,
+                flags, handler, callback);
+    }
+
+    /**
+     * Creates a virtual display.
+     * <p>
+     * The content of a virtual display is rendered to a {@link Surface} provided
+     * by the application.
+     * </p><p>
+     * The virtual display should be {@link VirtualDisplay#release released}
+     * when no longer needed.  Because a virtual display renders to a surface
+     * provided by the application, it will be released automatically when the
+     * process terminates and all remaining windows on it will be forcibly removed.
+     * </p><p>
+     * The behavior of the virtual display depends on the flags that are provided
+     * to this method.  By default, virtual displays are created to be private,
+     * non-presentation and unsecure.  Permissions may be required to use certain flags.
+     * </p><p>
+     * As of {@link android.os.Build.VERSION_CODES#KITKAT_WATCH}, the surface may
+     * be attached or detached dynamically using {@link VirtualDisplay#setSurface}.
+     * Previously, the surface had to be non-null when {@link #createVirtualDisplay}
+     * was called and could not be changed for the lifetime of the display.
+     * </p><p>
+     * Detaching the surface that backs a virtual display has a similar effect to
+     * turning off the screen.
+     * </p>
+     *
+     * @param name The name of the virtual display, must be non-empty.
+     * @param width The width of the virtual display in pixels, must be greater than 0.
+     * @param height The height of the virtual display in pixels, must be greater than 0.
+     * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
+     * @param requestedRefreshRate The requested refresh rate in frames per second.
+     * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
+     * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded
+     * up or down to a divisor of the physical display. If 0 is specified, the virtual
+     * display is refreshed at the physical display refresh rate.
+     * @param surface The surface to which the content of the virtual display should
+     * be rendered, or null if there is none initially.
+     * @param flags A combination of virtual display flags:
+     * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION},
+     * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
+     * or {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
+     * @param handler The handler on which the listener should be invoked, or null
+     * if the listener should be invoked on the calling thread's looper.
+     * @param callback Callback to call when the state of the {@link VirtualDisplay} changes
+     * @return The newly created virtual display, or null if the application could
+     * not create the virtual display.
+     *
+     * @throws SecurityException if the caller does not have permission to create
+     * a virtual display with the specified flags.
+     */
+    @Nullable
+    public VirtualDisplay createVirtualDisplay(@NonNull String name,
+            @IntRange(from = 1) int width,
+            @IntRange(from = 1) int height,
+            @IntRange(from = 1) int densityDpi,
+            float requestedRefreshRate,
+            @Nullable Surface surface,
+            @VirtualDisplayFlag int flags,
+            @Nullable Handler handler,
+            @Nullable VirtualDisplay.Callback callback) {
+        if (!ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE && requestedRefreshRate != 0.0f) {
+            Slog.e(TAG, "Please turn on ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE to use the new api");
+            return null;
+        }
+
         final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
                 height, densityDpi);
         builder.setFlags(flags);
         if (surface != null) {
             builder.setSurface(surface);
         }
+        if (requestedRefreshRate != 0.0f) {
+            builder.setRequestedRefreshRate(requestedRefreshRate);
+        }
         return createVirtualDisplay(null /* projection */, builder.build(), callback, handler,
                 null /* windowContext */);
     }
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index abd647f..f6a2e33 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -109,6 +109,13 @@
     @DataClass.PluralOf("displayCategory")
     @NonNull private List<String> mDisplayCategories = new ArrayList<>();
 
+    /**
+     * The refresh rate of a virtual display in frames per second.
+     * If this value is non-zero, this is the requested refresh rate to set.
+     * If this value is zero, the system chooses a default refresh rate.
+     */
+    private float mRequestedRefreshRate = 0.0f;
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -135,7 +142,8 @@
             @Nullable String uniqueId,
             int displayIdToMirror,
             boolean windowManagerMirroring,
-            @NonNull List<String> displayCategories) {
+            @NonNull List<String> displayCategories,
+            float requestedRefreshRate) {
         this.mName = name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mName);
@@ -161,6 +169,7 @@
         this.mDisplayCategories = displayCategories;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mDisplayCategories);
+        this.mRequestedRefreshRate = requestedRefreshRate;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -256,6 +265,16 @@
         return mDisplayCategories;
     }
 
+    /**
+     * The refresh rate of a virtual display in frames per second.
+     * If this value is none zero, this is the requested refresh rate to set.
+     * If this value is zero, the system chooses a default refresh rate.
+     */
+    @DataClass.Generated.Member
+    public float getRequestedRefreshRate() {
+        return mRequestedRefreshRate;
+    }
+
     @Override
     @DataClass.Generated.Member
     public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -276,6 +295,7 @@
         if (mUniqueId != null) dest.writeString(mUniqueId);
         dest.writeInt(mDisplayIdToMirror);
         dest.writeStringList(mDisplayCategories);
+        dest.writeFloat(mRequestedRefreshRate);
     }
 
     @Override
@@ -301,6 +321,7 @@
         int displayIdToMirror = in.readInt();
         List<String> displayCategories = new ArrayList<>();
         in.readStringList(displayCategories);
+        float requestedRefreshRate = in.readFloat();
 
         this.mName = name;
         com.android.internal.util.AnnotationValidations.validate(
@@ -327,6 +348,7 @@
         this.mDisplayCategories = displayCategories;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mDisplayCategories);
+        this.mRequestedRefreshRate = requestedRefreshRate;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -362,6 +384,7 @@
         private int mDisplayIdToMirror;
         private boolean mWindowManagerMirroring;
         private @NonNull List<String> mDisplayCategories;
+        private float mRequestedRefreshRate;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -528,10 +551,23 @@
             return this;
         }
 
+        /**
+         * The refresh rate of a virtual display in frames per second.
+         * If this value is none zero, this is the requested refresh rate to set.
+         * If this value is zero, the system chooses a default refresh rate.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setRequestedRefreshRate(float value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x400;
+            mRequestedRefreshRate = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull VirtualDisplayConfig build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x400; // Mark builder used
+            mBuilderFieldsSet |= 0x800; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x10) == 0) {
                 mFlags = 0;
@@ -551,6 +587,9 @@
             if ((mBuilderFieldsSet & 0x200) == 0) {
                 mDisplayCategories = new ArrayList<>();
             }
+            if ((mBuilderFieldsSet & 0x400) == 0) {
+                mRequestedRefreshRate = 0.0f;
+            }
             VirtualDisplayConfig o = new VirtualDisplayConfig(
                     mName,
                     mWidth,
@@ -561,12 +600,13 @@
                     mUniqueId,
                     mDisplayIdToMirror,
                     mWindowManagerMirroring,
-                    mDisplayCategories);
+                    mDisplayCategories,
+                    mRequestedRefreshRate);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x400) != 0) {
+            if ((mBuilderFieldsSet & 0x800) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -574,10 +614,10 @@
     }
 
     @DataClass.Generated(
-            time = 1668534501320L,
+            time = 1671047069703L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate  int mDisplayIdToMirror\nprivate  boolean mWindowManagerMirroring\nprivate @com.android.internal.util.DataClass.PluralOf(\"displayCategory\") @android.annotation.NonNull java.util.List<java.lang.String> mDisplayCategories\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
+            inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate  int mDisplayIdToMirror\nprivate  boolean mWindowManagerMirroring\nprivate @com.android.internal.util.DataClass.PluralOf(\"displayCategory\") @android.annotation.NonNull java.util.List<java.lang.String> mDisplayCategories\nprivate  float mRequestedRefreshRate\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/hardware/hdmi/DeviceFeatures.java b/core/java/android/hardware/hdmi/DeviceFeatures.java
index dc530ff..3805b55 100644
--- a/core/java/android/hardware/hdmi/DeviceFeatures.java
+++ b/core/java/android/hardware/hdmi/DeviceFeatures.java
@@ -130,12 +130,12 @@
         byte result = 0;
 
         if (mRecordTvScreenSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 6);
-        if (mSetOsdStringSupport == FEATURE_SUPPORTED) result = (byte) (1 << 5);
-        if (mDeckControlSupport == FEATURE_SUPPORTED) result = (byte) (1 << 4);
-        if (mSetAudioRateSupport == FEATURE_SUPPORTED) result = (byte) (1 << 3);
-        if (mArcTxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 2);
-        if (mArcRxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 1);
-        if (mSetAudioVolumeLevelSupport == FEATURE_SUPPORTED) result = (byte) 1;
+        if (mSetOsdStringSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 5);
+        if (mDeckControlSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 4);
+        if (mSetAudioRateSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 3);
+        if (mArcTxSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 2);
+        if (mArcRxSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 1);
+        if (mSetAudioVolumeLevelSupport == FEATURE_SUPPORTED) result |= (byte) 1;
 
         return new byte[]{ result };
     }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 249f486..832f23c 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -61,6 +61,17 @@
     /** The name of the overall product. */
     public static final String PRODUCT = getString("ro.product.name");
 
+    /**
+     * The product name for attestation. In non-default builds (like the AOSP build) the value of
+     * the 'PRODUCT' system property may be different to the one provisioned to KeyMint,
+     * and Keymint attestation would still attest to the product name, it's running on.
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    public static final String PRODUCT_FOR_ATTESTATION =
+            getString("ro.product.name_for_attestation");
+
     /** The name of the industrial design. */
     public static final String DEVICE = getString("ro.product.device");
 
@@ -89,9 +100,31 @@
     /** The consumer-visible brand with which the product/hardware will be associated, if any. */
     public static final String BRAND = getString("ro.product.brand");
 
+    /**
+     * The product brand for attestation. In non-default builds (like the AOSP build) the value of
+     * the 'BRAND' system property may be different to the one provisioned to KeyMint,
+     * and Keymint attestation would still attest to the product brand, it's running on.
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    public static final String BRAND_FOR_ATTESTATION =
+                getString("ro.product.brand_for_attestation");
+
     /** The end-user-visible name for the end product. */
     public static final String MODEL = getString("ro.product.model");
 
+    /**
+     * The product model for attestation. In non-default builds (like the AOSP build) the value of
+     * the 'MODEL' system property may be different to the one provisioned to KeyMint,
+     * and Keymint attestation would still attest to the product model, it's running on.
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    public static final String MODEL_FOR_ATTESTATION =
+                getString("ro.product.model_for_attestation");
+
     /** The manufacturer of the device's primary system-on-chip. */
     @NonNull
     public static final String SOC_MANUFACTURER = SocProperties.soc_manufacturer().orElse(UNKNOWN);
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index adc73c8..0d0d1da 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -110,7 +110,8 @@
     public static final long TRACE_TAG_THERMAL = 1L << 27;
 
     private static final long TRACE_TAG_NOT_READY = 1L << 63;
-    private static final int MAX_SECTION_NAME_LEN = 127;
+    /** @hide **/
+    public static final int MAX_SECTION_NAME_LEN = 127;
 
     // Must be volatile to avoid word tearing.
     // This is only kept in case any apps get this by reflection but do not
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d63d87d..0efd264 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2976,6 +2976,29 @@
     }
 
     /**
+     * @hide
+     */
+    public static boolean isVisibleBackgroundUsersOnDefaultDisplayEnabled() {
+        return SystemProperties.getBoolean("fw.visible_bg_users_on_default_display",
+                Resources.getSystem()
+                        .getBoolean(R.bool.config_multiuserVisibleBackgroundUsersOnDefaultDisplay));
+    }
+
+    /**
+     * Returns whether the device allows (full) users to be started in background visible in the
+     * {@link android.view.Display#DEFAULT_DISPLAY default display}.
+     *
+     * @return {@code false} for most devices, except passenger-only automotive build (i.e., when
+     * Android runs in a separate system in the back seat to manage the passenger displays).
+     *
+     * @hide
+     */
+    @TestApi
+    public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported() {
+        return isVisibleBackgroundUsersOnDefaultDisplayEnabled();
+    }
+
+    /**
      * Checks if the user is visible at the moment.
      *
      * <p>Roughly speaking, a "visible user" is a user that can present UI on at least one display.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b2d89962..4f96805 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7392,6 +7392,9 @@
          *
          * Format like "ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0"
          * where imeId is ComponentName and subtype is int32.
+         *
+         * <p>Note: This setting is not readable to the app targeting API level 34 or higher. use
+         * {@link android.view.inputmethod.InputMethodManager#getEnabledInputMethodList()} instead.
          */
         @Readable(maxTargetSdk = Build.VERSION_CODES.TIRAMISU)
         public static final String ENABLED_INPUT_METHODS = "enabled_input_methods";
@@ -15498,6 +15501,25 @@
         public static final String AUDIO_SAFE_VOLUME_STATE = "audio_safe_volume_state";
 
         /**
+         * Persisted safe hearding current CSD value. Values are stored as float percentages where
+         * 1.f represents 100% sound dose has been reached.
+         * @hide
+         */
+        public static final String AUDIO_SAFE_CSD_CURRENT_VALUE = "audio_safe_csd_current_value";
+
+        /**
+         * Persisted safe hearding next CSD warning value. Values are stored as float percentages.
+         * @hide
+         */
+        public static final String AUDIO_SAFE_CSD_NEXT_WARNING = "audio_safe_csd_next_warning";
+
+        /**
+         * Persisted safe hearding dose records (see {@link android.media.SoundDoseRecord})
+         * @hide
+         */
+        public static final String AUDIO_SAFE_CSD_DOSE_RECORDS = "audio_safe_csd_dose_records";
+
+        /**
          * URL for tzinfo (time zone) updates
          * @hide
          */
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index fde0afb..2e455c2 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
+import android.media.AudioSystem;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
@@ -129,7 +130,7 @@
 
         @Override
         public void updateAudioFlinger(IBinder audioFlinger) {
-            Log.v(TAG, "Ignore #updateAudioFlinger");
+            AudioSystem.setAudioFlingerBinder(audioFlinger);
         }
 
         @Override
diff --git a/core/java/android/text/Highlights.java b/core/java/android/text/Highlights.java
index 356dfca..ac4a5f2 100644
--- a/core/java/android/text/Highlights.java
+++ b/core/java/android/text/Highlights.java
@@ -38,9 +38,9 @@
     }
 
     /**
-     * Returns a number of highlight.
+     * Returns the number of highlight.
      *
-     * @return a number of highlight.
+     * @return the number of highlight.
      *
      * @see Builder#addRange(Paint, int, int)
      * @see Builder#addRanges(Paint, int...)
@@ -53,7 +53,7 @@
      * Returns a paint used for the i-th highlight.
      *
      * @param index an index of the highlight. Must be between 0 and {@link #getSize()}
-     * @return a paint object
+     * @return the paint object
      *
      * @see Builder#addRange(Paint, int, int)
      * @see Builder#addRanges(Paint, int...)
@@ -72,7 +72,7 @@
      * [1, 2, 3, 4].
      *
      * @param index an index of the highlight. Must be between 0 and {@link #getSize()}
-     * @return a paint object
+     * @return the flattened ranges.
      *
      * @see Builder#addRange(Paint, int, int)
      * @see Builder#addRanges(Paint, int...)
@@ -90,6 +90,9 @@
         /**
          * Add single range highlight.
          *
+         * If the given range has overlaps with the already added ranges, the previous highlights
+         * are overdrawn by this range.
+         *
          * @param paint a paint object used for drawing highlight path.
          * @param start an inclusive offset of the text.
          * @param end an exclusive offset of the text.
@@ -111,6 +114,9 @@
         /**
          * Add multiple ranges highlight.
          *
+         * If the given ranges have overlap with the already added ranges, the previous highlights
+         * are overdrawn by this range.
+         *
          * @param paint a paint object used for drawing highlight path.
          * @param ranges a flatten ranges. The {@code 2 * i}-th element is an inclusive start offset
          *              of the {@code i}-th character. The {@code 2 * i + 1}-th element is an
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 06fe523..b98deba 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -205,7 +205,7 @@
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
-        DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
+        DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
         DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "false");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index b22a43e..720f569 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -448,6 +448,7 @@
      */
     public static final int KEYBOARD_TYPE_ALPHABETIC = 2;
 
+    // Cap motion ranges to prevent attacks (b/25637534)
     private static final int MAX_RANGES = 1000;
 
     private static final int VIBRATOR_ID_ALL = -1;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1eb87c9..0bce710 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER;
 import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -51,6 +52,8 @@
 
 import com.android.internal.view.SurfaceCallbackHelper;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.locks.ReentrantLock;
@@ -117,6 +120,36 @@
  * may not blend properly as a consequence of not applying alpha to the surface content directly.
  */
 public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCallback {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SURFACE_LIFECYCLE_"},
+            value = {SURFACE_LIFECYCLE_DEFAULT,
+                     SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY,
+                     SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT})
+    public @interface SurfaceLifecycleStrategy {}
+
+    /**
+     * Default lifecycle of the Surface owned by this SurfaceView.
+     *
+     * The default lifecycle matches {@link #SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY}.
+     */
+    public static final int SURFACE_LIFECYCLE_DEFAULT = 0;
+
+    /**
+     * The Surface lifecycle is tied to SurfaceView visibility.
+     *
+     * The Surface is created when the SurfaceView becomes visible, and is destroyed when the
+     * SurfaceView is no longer visible.
+     */
+    public static final int SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY = 1;
+
+    /**
+     * The Surface lifecycle is tied to SurfaceView attachment.
+     * The Surface is created when the SurfaceView first becomes attached, but is not destroyed
+     * until this SurfaceView has been detached from the current window.
+     */
+    public static final int SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT = 2;
+
     private static final String TAG = "SurfaceView";
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_POSITION = false;
@@ -151,6 +184,11 @@
     SurfaceControl mBackgroundControl;
     private boolean mDisableBackgroundLayer = false;
 
+    @SurfaceLifecycleStrategy
+    private int mRequestedSurfaceLifecycleStrategy = SURFACE_LIFECYCLE_DEFAULT;
+    @SurfaceLifecycleStrategy
+    private int mSurfaceLifecycleStrategy = SURFACE_LIFECYCLE_DEFAULT;
+
     /**
      * We use this lock to protect access to mSurfaceControl. Both are accessed on the UI
      * thread and the render thread via RenderNode.PositionUpdateListener#positionLost.
@@ -413,6 +451,7 @@
             observer.removeOnPreDrawListener(mDrawListener);
             mGlobalListenersAdded = false;
         }
+        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " Detaching SV");
 
         mRequestedVisible = false;
 
@@ -699,6 +738,20 @@
         }
     }
 
+    /**
+     * Controls the lifecycle of the Surface owned by this SurfaceView.
+     *
+     * <p>By default, the lifecycycle strategy employed by the SurfaceView is
+     * {@link #SURFACE_LIFECYCLE_DEFAULT}.
+     *
+     * @param lifecycleStrategy The strategy for the lifecycle of the Surface owned by this
+     * SurfaceView.
+     */
+    public void setSurfaceLifecycle(@SurfaceLifecycleStrategy int lifecycleStrategy) {
+        mRequestedSurfaceLifecycleStrategy = lifecycleStrategy;
+        updateSurface();
+    }
+
     private void updateOpaqueFlag() {
         if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
             mSurfaceFlags |= SurfaceControl.OPAQUE;
@@ -780,7 +833,7 @@
 
         mSurfaceLock.lock();
         try {
-            mDrawingStopped = !mVisible;
+            mDrawingStopped = !surfaceShouldExist();
 
             if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                     + "Cur surface: " + mSurface);
@@ -887,6 +940,20 @@
         return realSizeChanged;
     }
 
+    private boolean requiresSurfaceControlCreation(boolean formatChanged, boolean visibleChanged) {
+        if (mSurfaceLifecycleStrategy == SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT) {
+            return (mSurfaceControl == null || formatChanged) && mAttachedToWindow;
+        }
+
+        return (mSurfaceControl == null || formatChanged || visibleChanged) && mRequestedVisible;
+    }
+
+    private boolean surfaceShouldExist() {
+        final boolean respectVisibility =
+                mSurfaceLifecycleStrategy != SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT;
+        return mVisible || (!respectVisibility && mAttachedToWindow);
+    }
+
     /** @hide */
     protected void updateSurface() {
         if (!mHaveFrame) {
@@ -921,8 +988,7 @@
         final boolean formatChanged = mFormat != mRequestedFormat;
         final boolean visibleChanged = mVisible != mRequestedVisible;
         final boolean alphaChanged = mAlpha != alpha;
-        final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
-                && mRequestedVisible;
+        final boolean creating = requiresSurfaceControlCreation(formatChanged, visibleChanged);
         final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
         final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
         getLocationInWindow(mLocation);
@@ -933,10 +999,13 @@
         final boolean hintChanged = (viewRoot.getBufferTransformHint() != mTransformHint)
                 && mRequestedVisible;
         final boolean relativeZChanged = mSubLayer != mRequestedSubLayer;
+        final boolean surfaceLifecycleStrategyChanged =
+                mSurfaceLifecycleStrategy != mRequestedSurfaceLifecycleStrategy;
 
         if (creating || formatChanged || sizeChanged || visibleChanged
                 || alphaChanged || windowVisibleChanged || positionChanged
-                || layoutSizeChanged || hintChanged || relativeZChanged) {
+                || layoutSizeChanged || hintChanged || relativeZChanged || !mAttachedToWindow
+                || surfaceLifecycleStrategyChanged) {
 
             if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                     + "Changes: creating=" + creating
@@ -945,7 +1014,10 @@
                     + " hint=" + hintChanged
                     + " visible=" + visibleChanged
                     + " left=" + (mWindowSpaceLeft != mLocation[0])
-                    + " top=" + (mWindowSpaceTop != mLocation[1]));
+                    + " top=" + (mWindowSpaceTop != mLocation[1])
+                    + " z=" + relativeZChanged
+                    + " attached=" + mAttachedToWindow
+                    + " lifecycleStrategy=" + surfaceLifecycleStrategyChanged);
 
             try {
                 mVisible = mRequestedVisible;
@@ -959,6 +1031,9 @@
                 mTransformHint = viewRoot.getBufferTransformHint();
                 mSubLayer = mRequestedSubLayer;
 
+                final int previousSurfaceLifecycleStrategy = mSurfaceLifecycleStrategy;
+                mSurfaceLifecycleStrategy = mRequestedSurfaceLifecycleStrategy;
+
                 mScreenRect.left = mWindowSpaceLeft;
                 mScreenRect.top = mWindowSpaceTop;
                 mScreenRect.right = mWindowSpaceLeft + getWidth();
@@ -1000,15 +1075,27 @@
                     SurfaceHolder.Callback[] callbacks = null;
 
                     final boolean surfaceChanged = creating;
-                    if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
-                        mSurfaceCreated = false;
-                        notifySurfaceDestroyed();
+                    final boolean respectVisibility =
+                            mSurfaceLifecycleStrategy != SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT;
+                    final boolean previouslyDidNotRespectVisibility =
+                            previousSurfaceLifecycleStrategy
+                                    == SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT;
+                    final boolean lifecycleNewlyRespectsVisibility = respectVisibility
+                            && previouslyDidNotRespectVisibility;
+                    if (mSurfaceCreated) {
+                        if (surfaceChanged || (!respectVisibility && !mAttachedToWindow)
+                                || (respectVisibility && !mVisible
+                                        && (visibleChanged || lifecycleNewlyRespectsVisibility))) {
+                            mSurfaceCreated = false;
+                            notifySurfaceDestroyed();
+                        }
                     }
 
                     copySurface(creating /* surfaceControlCreated */, sizeChanged);
 
-                    if (mVisible && mSurface.isValid()) {
-                        if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+                    if (surfaceShouldExist() && mSurface.isValid()) {
+                        if (!mSurfaceCreated
+                                && (surfaceChanged || (respectVisibility && visibleChanged))) {
                             mSurfaceCreated = true;
                             mIsCreating = true;
                             if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
@@ -1019,7 +1106,7 @@
                             }
                         }
                         if (creating || formatChanged || sizeChanged || hintChanged
-                                || visibleChanged || realSizeChanged) {
+                                || (respectVisibility && visibleChanged) || realSizeChanged) {
                             if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                                     + "surfaceChanged -- format=" + mFormat
                                     + " w=" + myWidth + " h=" + myHeight);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9fa6c05..a5693f3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.content.res.Resources.ID_NULL;
+import static android.os.Trace.TRACE_TAG_APP;
 import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
@@ -998,6 +999,22 @@
     private static boolean sAcceptZeroSizeDragShadow;
 
     /**
+     * When true, measure and layout passes of all the newly attached views will be logged with
+     * {@link Trace}, so we can better debug jank due to complex view hierarchies.
+     */
+    private static boolean sTraceLayoutSteps;
+
+    /**
+     * When not null, emits a {@link Trace} instant event and the stacktrace every time a relayout
+     * of a class having this name happens.
+     */
+    private static String sTraceRequestLayoutClass;
+
+    /** Used to avoid computing the full strings each time when layout tracing is enabled. */
+    @Nullable
+    private ViewTraversalTracingStrings mTracingStrings;
+
+    /**
      * Prior to R, {@link #dispatchApplyWindowInsets} had an issue:
      * <p>The modified insets changed by {@link #onApplyWindowInsets} were passed to the
      * entire view hierarchy in prefix order, including siblings as well as siblings of parents
@@ -3583,6 +3600,9 @@
      *                  1               PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE
      *                 1                PFLAG4_DRAG_A11Y_STARTED
      *                1                 PFLAG4_AUTO_HANDWRITING_INITIATION_ENABLED
+     *               1                  PFLAG4_IMPORTANT_FOR_CREDENTIAL_MANAGER
+     *              1                   PFLAG4_TRAVERSAL_TRACING_ENABLED
+     *             1                    PFLAG4_RELAYOUT_TRACING_ENABLED
      * |-------|-------|-------|-------|
      */
 
@@ -3669,6 +3689,18 @@
      */
     private static final int PFLAG4_IMPORTANT_FOR_CREDENTIAL_MANAGER = 0x000020000;
 
+    /**
+     * When set, measure and layout passes of this view will be logged with {@link Trace}, so we
+     * can better debug jank due to complex view hierarchies.
+     */
+    private static final int PFLAG4_TRAVERSAL_TRACING_ENABLED = 0x000040000;
+
+    /**
+     * When set, emits a {@link Trace} instant event and stacktrace every time a requestLayout of
+     * this class happens.
+     */
+    private static final int PFLAG4_RELAYOUT_TRACING_ENABLED = 0x000080000;
+
     /* End of masks for mPrivateFlags4 */
 
     /** @hide */
@@ -6653,6 +6685,15 @@
         out.append(mRight);
         out.append(',');
         out.append(mBottom);
+        appendId(out);
+        if (mAutofillId != null) {
+            out.append(" aid="); out.append(mAutofillId);
+        }
+        out.append("}");
+        return out.toString();
+    }
+
+    void appendId(StringBuilder out) {
         final int id = getId();
         if (id != NO_ID) {
             out.append(" #");
@@ -6684,11 +6725,6 @@
                 }
             }
         }
-        if (mAutofillId != null) {
-            out.append(" aid="); out.append(mAutofillId);
-        }
-        out.append("}");
-        return out.toString();
     }
 
     /**
@@ -21154,6 +21190,14 @@
         if (isFocused()) {
             notifyFocusChangeToImeFocusController(true /* hasFocus */);
         }
+
+        if (sTraceLayoutSteps) {
+            setTraversalTracingEnabled(true);
+        }
+        if (sTraceRequestLayoutClass != null
+                && sTraceRequestLayoutClass.equals(getClass().getSimpleName())) {
+            setRelayoutTracingEnabled(true);
+        }
     }
 
     /**
@@ -24053,6 +24097,30 @@
         return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
     }
 
+    /**
+     * Enable measure/layout debugging on traces.
+     *
+     * @see Trace
+     * @hide
+     */
+    public static void setTraceLayoutSteps(boolean traceLayoutSteps) {
+        sTraceLayoutSteps = traceLayoutSteps;
+    }
+
+    /**
+     * Enable request layout tracing classes with {@code s} simple name.
+     * <p>
+     * When set, a {@link Trace} instant event and a log with the stacktrace is emitted every
+     * time a requestLayout of a class matching {@code s} name happens.
+     * This applies only to views attached from this point onwards.
+     *
+     * @see Trace#instant(long, String)
+     * @hide
+     */
+    public static void setTracedRequestLayoutClassClass(String s) {
+        sTraceRequestLayoutClass = s;
+    }
+
     private boolean setOpticalFrame(int left, int top, int right, int bottom) {
         Insets parentInsets = mParent instanceof View ?
                 ((View) mParent).getOpticalInsets() : Insets.NONE;
@@ -24087,7 +24155,13 @@
     @SuppressWarnings({"unchecked"})
     public void layout(int l, int t, int r, int b) {
         if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
+            if (isTraversalTracingEnabled()) {
+                Trace.beginSection(mTracingStrings.onMeasureBeforeLayout);
+            }
             onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
+            if (isTraversalTracingEnabled()) {
+                Trace.endSection();
+            }
             mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
         }
 
@@ -24100,7 +24174,13 @@
                 setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
 
         if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
+            if (isTraversalTracingEnabled()) {
+                Trace.beginSection(mTracingStrings.onLayout);
+            }
             onLayout(changed, l, t, r, b);
+            if (isTraversalTracingEnabled()) {
+                Trace.endSection();
+            }
 
             if (shouldDrawRoundScrollbar()) {
                 if(mRoundScrollbarRenderer == null) {
@@ -26660,6 +26740,25 @@
         return (viewRoot != null && viewRoot.isInLayout());
     }
 
+    /** To be used only for debugging purposes. */
+    private void printStackStrace(String name) {
+        Log.d(VIEW_LOG_TAG, "---- ST:" + name);
+
+        StringBuilder sb = new StringBuilder();
+        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
+        int startIndex = 1;
+        int endIndex = Math.min(stackTraceElements.length, startIndex + 20); // max 20 entries.
+        for (int i = startIndex; i < endIndex; i++) {
+            StackTraceElement s = stackTraceElements[i];
+            sb.append(s.getMethodName())
+                    .append("(")
+                    .append(s.getFileName())
+                    .append(":")
+                    .append(s.getLineNumber())
+                    .append(") <- ");
+        }
+        Log.d(VIEW_LOG_TAG, name + ": " + sb);
+    }
     /**
      * Call this when something has changed which has invalidated the
      * layout of this view. This will schedule a layout pass of the view
@@ -26673,6 +26772,12 @@
      */
     @CallSuper
     public void requestLayout() {
+        if (isRelayoutTracingEnabled()) {
+            Trace.instantForTrack(TRACE_TAG_APP, "requestLayoutTracing",
+                    mTracingStrings.classSimpleName);
+            printStackStrace(mTracingStrings.requestLayoutStacktracePrefix);
+        }
+
         if (mMeasureCache != null) mMeasureCache.clear();
 
         if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
@@ -26766,8 +26871,14 @@
 
             int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
             if (cacheIndex < 0 || sIgnoreMeasureCache) {
+                if (isTraversalTracingEnabled()) {
+                    Trace.beginSection(mTracingStrings.onMeasure);
+                }
                 // measure ourselves, this should set the measured dimension flag back
                 onMeasure(widthMeasureSpec, heightMeasureSpec);
+                if (isTraversalTracingEnabled()) {
+                    Trace.endSection();
+                }
                 mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
             } else {
                 long value = mMeasureCache.valueAt(cacheIndex);
@@ -32005,6 +32116,38 @@
                 .isStylusHandwritingAvailable();
     }
 
+    private void setTraversalTracingEnabled(boolean enabled) {
+        if (enabled) {
+            if (mTracingStrings == null) {
+                mTracingStrings = new ViewTraversalTracingStrings(this);
+            }
+            mPrivateFlags4 |= PFLAG4_TRAVERSAL_TRACING_ENABLED;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_TRAVERSAL_TRACING_ENABLED;
+        }
+    }
+
+    private boolean isTraversalTracingEnabled() {
+        return (mPrivateFlags4 & PFLAG4_TRAVERSAL_TRACING_ENABLED)
+                == PFLAG4_TRAVERSAL_TRACING_ENABLED;
+    }
+
+    private void setRelayoutTracingEnabled(boolean enabled) {
+        if (enabled) {
+            if (mTracingStrings == null) {
+                mTracingStrings = new ViewTraversalTracingStrings(this);
+            }
+            mPrivateFlags4 |= PFLAG4_RELAYOUT_TRACING_ENABLED;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_RELAYOUT_TRACING_ENABLED;
+        }
+    }
+
+    private boolean isRelayoutTracingEnabled() {
+        return (mPrivateFlags4 & PFLAG4_RELAYOUT_TRACING_ENABLED)
+                == PFLAG4_RELAYOUT_TRACING_ENABLED;
+    }
+
     /**
      * Collects a {@link ViewTranslationRequest} which represents the content to be translated in
      * the view.
diff --git a/core/java/android/view/ViewTraversalTracingStrings.java b/core/java/android/view/ViewTraversalTracingStrings.java
new file mode 100644
index 0000000..7dde87b
--- /dev/null
+++ b/core/java/android/view/ViewTraversalTracingStrings.java
@@ -0,0 +1,63 @@
+/*
+ * 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.view;
+
+import android.os.Trace;
+
+/**
+ * Keeps and caches strings used to trace {@link View} traversals.
+ * <p>
+ * This is done to avoid expensive computations of them every time, which can improve performance.
+ */
+class ViewTraversalTracingStrings {
+
+    /** {@link Trace} tag used to mark {@link View#onMeasure(int, int)}. */
+    public final String onMeasure;
+
+    /** {@link Trace} tag used to mark {@link View#onLayout(boolean, int, int, int, int)}. */
+    public final String onLayout;
+
+    /** Caches the view simple name to avoid re-computations. */
+    public final String classSimpleName;
+
+    /** Prefix for request layout stacktraces output in logs. */
+    public final String requestLayoutStacktracePrefix;
+
+    /** {@link Trace} tag used to mark {@link View#onMeasure(int, int)} happening before layout. */
+    public final String onMeasureBeforeLayout;
+
+    /**
+     * @param v {@link View} from where to get the class name.
+     */
+    ViewTraversalTracingStrings(View v) {
+        String className = v.getClass().getSimpleName();
+        classSimpleName = className;
+        onMeasureBeforeLayout = getTraceName("onMeasureBeforeLayout", className, v);
+        onMeasure = getTraceName("onMeasure", className, v);
+        onLayout = getTraceName("onLayout", className, v);
+        requestLayoutStacktracePrefix = "requestLayout " + className;
+    }
+
+    private String getTraceName(String sectionName, String className, View v) {
+        StringBuilder out = new StringBuilder();
+        out.append(sectionName);
+        out.append(" ");
+        out.append(className);
+        v.appendId(out);
+        return out.substring(0, Math.min(out.length() - 1, Trace.MAX_SECTION_NAME_LEN - 1));
+    }
+}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 9bec409..145a5d3 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -815,8 +815,8 @@
     }
 
     /**
-     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
-     * .Property} for an app to inform the system that the activity can be opted-in or opted-out
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the app can be opted-in or opted-out
      * from the compatibility treatment that avoids {@link
      * android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by
      * ignoreRequestedOrientation display setting enabled on the device or by the landscape natural
@@ -834,17 +834,17 @@
      *     <li>Camera compatibility force rotation treatment is active for the package.
      * </ul>
      *
-     * <p>Setting this property to {@code false} informs the system that the activity must be
+     * <p>Setting this property to {@code false} informs the system that the app must be
      * opted-out from the compatibility treatment even if the device manufacturer has opted the app
      * into the treatment.
      *
      * <p><b>Syntax:</b>
      * <pre>
-     * &lt;activity&gt;
+     * &lt;application&gt;
      *   &lt;property
      *     android:name="android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"
      *     android:value="true|false"/&gt;
-     * &lt;/activity&gt;
+     * &lt;/application&gt;
      * </pre>
      *
      * @hide
@@ -854,8 +854,8 @@
             "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
 
     /**
-     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
-     * .Property} for an app to inform the system that the activity should be excluded from the
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the app should be excluded from the
      * camera compatibility force rotation treatment.
      *
      * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
@@ -880,11 +880,11 @@
      *
      * <p><b>Syntax:</b>
      * <pre>
-     * &lt;activity&gt;
+     * &lt;application&gt;
      *   &lt;property
      *     android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"
      *     android:value="true|false"/&gt;
-     * &lt;/activity&gt;
+     * &lt;/application&gt;
      * </pre>
      *
      * @hide
@@ -894,8 +894,8 @@
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
 
     /**
-     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
-     * .Property} for an app to inform the system that the activity should be excluded
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the app should be excluded
      * from the activity "refresh" after the camera compatibility force rotation treatment.
      *
      * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
@@ -927,11 +927,11 @@
      *
      * <p><b>Syntax:</b>
      * <pre>
-     * &lt;activity&gt;
+     * &lt;application&gt;
      *   &lt;property
      *     android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"
      *     android:value="true|false"/&gt;
-     * &lt;/activity&gt;
+     * &lt;/application&gt;
      * </pre>
      *
      * @hide
@@ -941,7 +941,7 @@
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
 
     /**
-     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
      * .Property} for an app to inform the system that the activity should be or shouldn't be
      * "refreshed" after the camera compatibility force rotation treatment using "paused ->
      * resumed" cycle rather than "stopped -> resumed".
@@ -977,11 +977,11 @@
      *
      * <p><b>Syntax:</b>
      * <pre>
-     * &lt;activity&gt;
+     * &lt;application&gt;
      *   &lt;property
      *     android:name="android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"
      *     android:value="true|false"/&gt;
-     * &lt;/activity&gt;
+     * &lt;/application&gt;
      * </pre>
      *
      * @hide
@@ -991,23 +991,23 @@
             "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
 
     /**
-     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
-     * .Property} for an app to inform the system that the activity should be excluded from the
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the app should be excluded from the
      * compatibility override for orientation set by the device manufacturer.
      *
      * <p>With this property set to {@code true} or unset, device manufacturers can override
-     * orientation for the activity using their discretion to improve display compatibility.
+     * orientation for the app using their discretion to improve display compatibility.
      *
      * <p>With this property set to {@code false}, device manufactured per-app override for
      * orientation won't be applied.
      *
      * <p><b>Syntax:</b>
      * <pre>
-     * &lt;activity&gt;
+     * &lt;application&gt;
      *   &lt;property
      *     android:name="android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE"
      *     android:value="true|false"/&gt;
-     * &lt;/activity&gt;
+     * &lt;/application&gt;
      * </pre>
      *
      * @hide
@@ -1017,8 +1017,8 @@
             "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
 
     /**
-     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
-     * .Property} for an app to inform the system that the activity should be opted-out from the
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the app should be opted-out from the
      * compatibility override that fixes display orientation to landscape natural orientation when
      * an activity is fullscreen.
      *
@@ -1048,11 +1048,11 @@
      *
      * <p><b>Syntax:</b>
      * <pre>
-     * &lt;activity&gt;
+     * &lt;application&gt;
      *   &lt;property
      *     android:name="android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE"
      *     android:value="true|false"/&gt;
-     * &lt;/activity&gt;
+     * &lt;/application&gt;
      * </pre>
      *
      * @hide
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 1c18a73..d71e82f 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -37,6 +37,7 @@
 import android.content.UndoOwner;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -101,6 +102,7 @@
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -3128,6 +3130,9 @@
             }
         }
 
+        final int keyboard = mTextView.getResources().getConfiguration().keyboard;
+        menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY);
+
         menu.add(Menu.NONE, TextView.ID_UNDO, MENU_ITEM_ORDER_UNDO,
                 com.android.internal.R.string.undo)
                 .setAlphabeticShortcut('z')
@@ -3135,6 +3140,7 @@
                 .setEnabled(mTextView.canUndo());
         menu.add(Menu.NONE, TextView.ID_REDO, MENU_ITEM_ORDER_REDO,
                 com.android.internal.R.string.redo)
+                .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
                 .setOnMenuItemClickListener(mOnContextMenuItemClickListener)
                 .setEnabled(mTextView.canRedo());
 
@@ -3155,6 +3161,7 @@
                 .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
         menu.add(Menu.NONE, TextView.ID_PASTE_AS_PLAIN_TEXT, MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
                 com.android.internal.R.string.paste_as_plain_text)
+                .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
                 .setEnabled(mTextView.canPasteAsPlainText())
                 .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
         menu.add(Menu.NONE, TextView.ID_SHARE, MENU_ITEM_ORDER_SHARE,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 089545a..be58893 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -108,6 +108,7 @@
 import android.text.InputFilter;
 import android.text.InputType;
 import android.text.Layout;
+import android.text.NoCopySpan;
 import android.text.ParcelableSpan;
 import android.text.PrecomputedText;
 import android.text.SegmentFinder;
@@ -905,6 +906,8 @@
     private Scroller mScroller;
     private TextPaint mTempTextPaint;
 
+    private Object mTempCursor;
+
     @UnsupportedAppUsage
     private BoringLayout.Metrics mBoring;
     @UnsupportedAppUsage
@@ -10060,10 +10063,8 @@
         }
         int offset = mLayout.getOffsetForHorizontal(line, point.x);
         String textToInsert = gesture.getTextToInsert();
-        getEditableText().insert(offset, textToInsert);
-        Selection.setSelection(getEditableText(), offset + textToInsert.length());
+        return tryInsertTextForHandwritingGesture(offset, textToInsert, gesture);
         // TODO(b/243980426): Insert extra spaces if necessary.
-        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
     }
 
     /** @hide */
@@ -10163,12 +10164,11 @@
         if (startOffset < endOffset) {
             getEditableText().delete(startOffset, endOffset);
             Selection.setSelection(getEditableText(), startOffset);
+            return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
         } else {
             // No whitespace found, so insert a space.
-            getEditableText().insert(startOffset, " ");
-            Selection.setSelection(getEditableText(), startOffset + 1);
+            return tryInsertTextForHandwritingGesture(startOffset, " ", gesture);
         }
-        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
     }
 
     /** @hide */
@@ -10252,6 +10252,32 @@
                 area, segmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
     }
 
+    private int tryInsertTextForHandwritingGesture(
+            int offset, String textToInsert, HandwritingGesture gesture) {
+        // A temporary cursor span is placed at the insertion offset. The span will be pushed
+        // forward when text is inserted, then the real cursor can be placed after the inserted
+        // text. A temporary cursor span is used in order to avoid modifying the real selection span
+        // in the case that the text is filtered out.
+        Editable editableText = getEditableText();
+        if (mTempCursor == null) {
+            mTempCursor = new NoCopySpan.Concrete();
+        }
+        editableText.setSpan(mTempCursor, offset, offset, Spanned.SPAN_POINT_POINT);
+
+        editableText.insert(offset, textToInsert);
+
+        int newOffset = editableText.getSpanStart(mTempCursor);
+        editableText.removeSpan(mTempCursor);
+        if (newOffset == offset) {
+            // The inserted text was filtered out.
+            return handleGestureFailure(gesture);
+        } else {
+            // Place the cursor after the inserted text.
+            Selection.setSelection(editableText, newOffset);
+            return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
+        }
+    }
+
     private Pattern getWhitespacePattern() {
         if (mWhitespacePattern == null) {
             mWhitespacePattern = Pattern.compile("\\s+");
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 11bd47d..c497c94 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -56,7 +56,7 @@
 public final class WindowMetricsController {
     // TODO(b/151908239): Remove and always enable this if it is stable.
     private static final boolean LAZY_WINDOW_INSETS = android.os.SystemProperties.getBoolean(
-            "persist.wm.debug.win_metrics_lazy_insets", false);
+            "persist.wm.debug.win_metrics_lazy_insets", true);
     private final Context mContext;
 
     public WindowMetricsController(@NonNull Context context) {
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index f843318..fc5cb4b 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -229,6 +229,35 @@
         }
     }
 
+    /**
+     * Enables visual detection service.
+     *
+     * @param listener to receive visual attention gained/lost events.
+     */
+    public void enableVisualQueryDetection(
+            IVisualQueryDetectionAttentionListener listener) {
+        try {
+            if (mVoiceInteractionManagerService != null) {
+                mVoiceInteractionManagerService.enableVisualQueryDetection(listener);
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to register visual query detection attention listener", e);
+        }
+    }
+
+    /**
+     * Disables visual query detection.
+     */
+    public void disableVisualQueryDetection() {
+        try {
+            if (mVoiceInteractionManagerService != null) {
+                mVoiceInteractionManagerService.disableVisualQueryDetection();
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to register visual query detection attention listener", e);
+        }
+    }
+
     @UnsupportedAppUsage
     public ComponentName getAssistComponentForUser(int userId) {
         final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
diff --git a/core/java/com/android/internal/app/ISoundTriggerSession.aidl b/core/java/com/android/internal/app/ISoundTriggerSession.aidl
index a1ba558..3269ffc 100644
--- a/core/java/com/android/internal/app/ISoundTriggerSession.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerSession.aidl
@@ -36,7 +36,8 @@
 
     void deleteSoundModel(in ParcelUuid soundModelId);
 
-    int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback,
+    int startRecognition(in SoundTrigger.GenericSoundModel soundModel,
+         in IRecognitionStatusCallback callback,
          in SoundTrigger.RecognitionConfig config, boolean runInBatterySaver);
 
     int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
diff --git a/core/java/com/android/internal/app/IVisualQueryDetectionAttentionListener.aidl b/core/java/com/android/internal/app/IVisualQueryDetectionAttentionListener.aidl
new file mode 100644
index 0000000..3e48da7
--- /dev/null
+++ b/core/java/com/android/internal/app/IVisualQueryDetectionAttentionListener.aidl
@@ -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.internal.app;
+
+/**
+ * Allows sysui to notify users the assistant is ready to take a query without notifying the
+ * assistant app.
+ */
+oneway interface IVisualQueryDetectionAttentionListener {
+   /**
+    * Called when attention signal is sent.
+    */
+   void onAttentionGained();
+
+   /**
+    * Called when a attention signal is lost.
+    */
+   void onAttentionLost();
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 88d6425..619b420 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -39,6 +39,7 @@
 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
 import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 
 interface IVoiceInteractionManagerService {
     void showSession(in Bundle sessionArgs, int flags, String attributionTag);
@@ -300,6 +301,12 @@
      */
     void shutdownHotwordDetectionService();
 
+    @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
+    void enableVisualQueryDetection(in IVisualQueryDetectionAttentionListener Listener);
+
+    @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
+    void disableVisualQueryDetection();
+
     void startPerceiving(in IVisualQueryDetectionVoiceInteractionCallback callback);
 
     void stopPerceiving();
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index 56da8e0..69660ae 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -11,6 +11,7 @@
 per-file *Assist* = file:/core/java/android/service/voice/OWNERS
 per-file *Hotword* = file:/core/java/android/service/voice/OWNERS
 per-file *Voice* = file:/core/java/android/service/voice/OWNERS
+per-file *VisualQuery* = file:/core/java/android/service/voice/OWNERS
 
 # System language settings
 per-file *Locale* = file:platform/packages/apps/Settings:/src/com/android/settings/localepicker/OWNERS
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index fd0d9c5..128de8b 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -88,6 +88,9 @@
 
     optional SettingProto assisted_gps_enabled = 14 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto audio_safe_volume_state = 15 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto audio_safe_csd_current_value = 157 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto audio_safe_csd_next_warning = 158 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto audio_safe_csd_dose_records = 159 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     reserved 17; // Used to be autofill_compat_mode_allowed_packages
 
@@ -1087,5 +1090,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 157;
+    // Next tag = 160;
 }
diff --git a/core/res/res/drawable/ic_clone_badge.xml b/core/res/res/drawable/ic_clone_badge.xml
new file mode 100644
index 0000000..1682425
--- /dev/null
+++ b/core/res/res/drawable/ic_clone_badge.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:pathData="M22,9.5C22,13.642 18.642,17 14.5,17C10.358,17 7,13.642 7,9.5C7,5.358 10.358,2 14.5,2C18.642,2 22,5.358 22,9.5Z"
+        android:fillColor="@android:color/system_neutral2_800"/>
+    <path
+        android:pathData="M9.5,20.333C12.722,20.333 15.333,17.722 15.333,14.5C15.333,11.278 12.722,8.667 9.5,8.667C6.278,8.667 3.667,11.278 3.667,14.5C3.667,17.722 6.278,20.333 9.5,20.333ZM9.5,22C13.642,22 17,18.642 17,14.5C17,10.358 13.642,7 9.5,7C5.358,7 2,10.358 2,14.5C2,18.642 5.358,22 9.5,22Z"
+        android:fillColor="@android:color/system_neutral2_800"
+        android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_clone_icon_badge.xml b/core/res/res/drawable/ic_clone_icon_badge.xml
new file mode 100644
index 0000000..e4eabc8
--- /dev/null
+++ b/core/res/res/drawable/ic_clone_icon_badge.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="64dp"
+        android:height="64dp"
+        android:viewportWidth="64"
+        android:viewportHeight="64">
+<group
+    android:scaleX=".66"
+    android:scaleY=".66"
+    android:translateX="42"
+    android:translateY="42">
+    <path
+        android:pathData="M22,9.5C22,13.642 18.642,17 14.5,17C10.358,17 7,13.642 7,9.5C7,5.358 10.358,2 14.5,2C18.642,2 22,5.358 22,9.5Z"
+        android:fillColor="@android:color/system_neutral2_800"/>
+    <path
+        android:pathData="M9.5,20.333C12.722,20.333 15.333,17.722 15.333,14.5C15.333,11.278 12.722,8.667 9.5,8.667C6.278,8.667 3.667,11.278 3.667,14.5C3.667,17.722 6.278,20.333 9.5,20.333ZM9.5,22C13.642,22 17,18.642 17,14.5C17,10.358 13.642,7 9.5,7C5.358,7 2,10.358 2,14.5C2,18.642 5.358,22 9.5,22Z"
+        android:fillColor="@android:color/system_neutral2_800"
+        android:fillType="evenOdd"/>
+</group>
+</vector>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 9e735d0..c3366cf 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -207,4 +207,25 @@
          be set by OEMs for devices which use eUICCs. -->
     <integer-array name="non_removable_euicc_slots"></integer-array>
 
+    <!-- Device state identifiers and strings for system notifications. The string arrays must have
+         the same length and order as the identifier array. -->
+    <integer-array name="device_state_notification_state_identifiers">
+        <!-- TODO(b/267231269) change to concurrent display identifier when ready -->
+        <item>-1</item>
+    </integer-array>
+    <string-array name="device_state_notification_names">
+        <item>@string/concurrent_display_notification_name</item>
+    </string-array>
+    <string-array name="device_state_notification_active_titles">
+        <item>@string/concurrent_display_notification_active_title</item>
+    </string-array>
+    <string-array name="device_state_notification_active_contents">
+        <item>@string/concurrent_display_notification_active_content</item>
+    </string-array>
+    <string-array name="device_state_notification_thermal_titles">
+        <item>@string/concurrent_display_notification_thermal_title</item>
+    </string-array>
+    <string-array name="device_state_notification_thermal_contents">
+        <item>@string/concurrent_display_notification_thermal_content</item>
+    </string-array>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b8ce7e3..37d5b84 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2735,6 +2735,11 @@
          Should be false for most devices, except automotive vehicle with passenger displays. -->
     <bool name="config_multiuserVisibleBackgroundUsers">false</bool>
 
+    <!-- Whether the device allows users to start in background visible on the default display.
+         Should be false for most devices, except passenger-only automotive build (i.e., when
+         Android runs in a separate system in the back seat to manage the passenger displays) -->
+    <bool name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay">false</bool>
+
     <!-- Whether to automatically switch to the designated Dock User (the user chosen for
          displaying dreams, etc.) after a timeout when the device is docked.  -->
     <bool name="config_enableTimeoutToDockUserWhenDocked">false</bool>
@@ -5001,6 +5006,26 @@
       -->
     </integer-array>
 
+    <!-- The properties for handling UDFPS touch detection
+    <string-array name="config_udfps_touch_detection_options">
+        <item>[detector_type],[sensor_shape],[target_size],[min_ellipse_overlap_percentage]</item>
+    </string-array>
+
+        [detector_type]: 0 for bounding box detector, 1 for ellipse detector
+        [sensor_shape]: 0 for square, 1 for circle
+        [target_size]: percentage (defined as a float of 0-1) of sensor that is considered the target, expands outward from center
+        [min_ellipse_overlap_percentage]: minimum percentage (float from 0-1) needed to be considered a valid overlap
+        [step_size]: size of each step when iterating over sensor pixel grid
+    -->
+    <string-array name="config_udfps_touch_detection_options">
+        <item>0,0,1.0,0,1</item>
+        <item>1,1,1.0,0,1</item>
+        <item>1,1,1.0,.4,1</item>
+    </string-array>
+
+    <!-- The integer index of the selected option in config_udfps_touch_detection_options -->
+    <integer name="config_selected_udfps_touch_detection">2</integer>
+
     <!-- An array of arrays of side fingerprint sensor properties relative to each display.
          Note: this value is temporary and is expected to be queried directly
          from the HAL in the future. -->
@@ -6210,4 +6235,27 @@
         trusted certificate using the SHA-256 digest algorithm. -->
     <string-array name="config_healthConnectMigrationKnownSigners">
     </string-array>
+
+    <!-- The Universal Stylus Initiative (USI) protocol version supported by each display.
+         (@see https://universalstylus.org/).
+
+         The i-th value in this array corresponds to the supported USI version of the i-th display
+         listed in config_displayUniqueIdArray. On a single-display device, the
+         config_displayUniqueIdArray may be empty, in which case the only value in this array should
+         be the USI version for the main built-in display.
+
+         If the display does not support USI, the version value should be an empty string. If the
+         display supports USI, the version must be in the following format:
+           "<major-version>.<minor-version>"
+
+         For example, "", "1.0", and "2.0" are valid values. -->
+    <string-array name="config_displayUsiVersionArray" translatable="false">
+        <item>""</item>
+    </string-array>
+
+    <!-- Whether system apps should be scanned in the stopped state during initial boot.
+        Packages can be added by OEMs in an allowlist, to prevent them from being scanned as
+        "stopped" during initial boot of a device, or after an OTA update. Stopped state of
+        an app is not changed during subsequent reboots.  -->
+    <bool name="config_stopSystemPackagesByDefault">false</bool>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e330ee3..da44c2e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5067,6 +5067,13 @@
     <string name="managed_profile_label_badge_2">2nd Work <xliff:g id="label" example="Email">%1$s</xliff:g></string>
     <string name="managed_profile_label_badge_3">3rd Work <xliff:g id="label" example="Email">%1$s</xliff:g></string>
 
+    <!--
+        Used to wrap a label for content description for a Clone profile, e.g. "Clone Messenger"
+        instead of Messenger when the Messenger app is cloned.
+        [CHAR LIMIT=20]
+    -->
+    <string name="clone_profile_label_badge">Clone <xliff:g id="label" example="Messenger">%1$s</xliff:g></string>
+
     <!-- DO NOT TRANSLATE -->
     <string name="time_placeholder">--</string>
 
@@ -6234,4 +6241,17 @@
     <string name="mic_access_on_toast">Microphone is available</string>
     <!-- Toast message that is shown when the user mutes the microphone from the keyboard. [CHAR LIMIT=TOAST] -->
     <string name="mic_access_off_toast">Microphone is blocked</string>
+
+    <!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_name">Dual screen</string>
+    <!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_active_title">Dual screen is on</string>
+    <!-- Content of concurrent display active notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_active_content"><xliff:g id="app_name" example="Camera app">%1$s</xliff:g> is using both displays to show content</string>
+    <!-- Title of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_thermal_title">Device is too warm</string>
+    <!-- Content of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string>
+    <!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] -->
+    <string name="device_state_notification_turn_off_button">Turn off</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 76af814..2af91e1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -473,6 +473,7 @@
   <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
   <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
   <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsers" />
+  <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay" />
   <java-symbol type="bool" name="config_enableTimeoutToDockUserWhenDocked" />
   <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
   <java-symbol type="xml" name="config_user_types" />
@@ -1063,6 +1064,7 @@
   <java-symbol type="string" name="managed_profile_label_badge" />
   <java-symbol type="string" name="managed_profile_label_badge_2" />
   <java-symbol type="string" name="managed_profile_label_badge_3" />
+  <java-symbol type="string" name="clone_profile_label_badge" />
   <java-symbol type="string" name="mediasize_unknown_portrait" />
   <java-symbol type="string" name="mediasize_unknown_landscape" />
   <java-symbol type="string" name="mediasize_iso_a0" />
@@ -1386,6 +1388,8 @@
   <java-symbol type="drawable" name="ic_qs_auto_rotate" />
   <java-symbol type="drawable" name="ic_qs_dnd" />
   <java-symbol type="drawable" name="ic_qs_one_handed_mode" />
+  <java-symbol type="drawable" name="ic_clone_icon_badge" />
+  <java-symbol type="drawable" name="ic_clone_badge" />
 
   <java-symbol type="drawable" name="sim_light_blue" />
   <java-symbol type="drawable" name="sim_light_green" />
@@ -2646,6 +2650,8 @@
   <java-symbol type="array" name="config_biometric_sensors" />
   <java-symbol type="bool" name="allow_test_udfps" />
   <java-symbol type="array" name="config_udfps_sensor_props" />
+  <java-symbol type="array" name="config_udfps_touch_detection_options" />
+  <java-symbol type="integer" name="config_selected_udfps_touch_detection" />
   <java-symbol type="array" name="config_sfps_sensor_props" />
   <java-symbol type="bool" name="config_is_powerbutton_fps" />
   <java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
@@ -4874,6 +4880,18 @@
   <java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
   <java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
   <java-symbol type="integer" name="config_deviceStateRearDisplay"/>
+  <java-symbol type="array" name="device_state_notification_state_identifiers"/>
+  <java-symbol type="array" name="device_state_notification_names"/>
+  <java-symbol type="array" name="device_state_notification_active_titles"/>
+  <java-symbol type="array" name="device_state_notification_active_contents"/>
+  <java-symbol type="array" name="device_state_notification_thermal_titles"/>
+  <java-symbol type="array" name="device_state_notification_thermal_contents"/>
+  <java-symbol type="string" name="concurrent_display_notification_name"/>
+  <java-symbol type="string" name="concurrent_display_notification_active_title"/>
+  <java-symbol type="string" name="concurrent_display_notification_active_content"/>
+  <java-symbol type="string" name="concurrent_display_notification_thermal_title"/>
+  <java-symbol type="string" name="concurrent_display_notification_thermal_content"/>
+  <java-symbol type="string" name="device_state_notification_turn_off_button"/>
   <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
 
   <!-- For app language picker -->
@@ -4892,4 +4910,6 @@
   <java-symbol type="string" name="config_mainDisplayShape"/>
   <java-symbol type="string" name="config_secondaryDisplayShape"/>
   <java-symbol type="array" name="config_displayShapeArray" />
+
+  <java-symbol type="bool" name="config_stopSystemPackagesByDefault"/>
 </resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 4548730..34d669b 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -31,6 +31,7 @@
 import static android.app.Notification.EXTRA_MEDIA_REMOTE_INTENT;
 import static android.app.Notification.EXTRA_MEDIA_SESSION;
 import static android.app.Notification.EXTRA_MESSAGING_PERSON;
+import static android.app.Notification.EXTRA_PEOPLE_LIST;
 import static android.app.Notification.EXTRA_PICTURE;
 import static android.app.Notification.EXTRA_PICTURE_ICON;
 import static android.app.Notification.MessagingStyle.Message.KEY_DATA_URI;
@@ -817,6 +818,23 @@
     }
 
     @Test
+    public void testRecoverBuilder_nullExtraPeopleList_noCrash() {
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_PEOPLE_LIST, null);
+
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(0)
+                .addExtras(extras)
+                .build();
+        Bundle fakeTypes = new Bundle();
+        fakeTypes.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, new Bundle());
+
+        Notification.Builder.recoverBuilder(mContext, n);
+
+        // no crash, good
+    }
+
+    @Test
     public void testVisitUris_invalidExtra_noCrash() {
         Notification n = new Notification.Builder(mContext, "test")
                 .setSmallIcon(0)
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
index 8bed31f..6636cbd 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -44,10 +44,12 @@
     public void testEquals() {
         TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
         TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
         {
@@ -70,6 +72,20 @@
             assertEquals(one, two);
         }
 
+        builder2.setUseLocationEnabled(false);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setUseLocationEnabled(false);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertEquals(one, two);
+        }
+
         builder2.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
         {
             TimeZoneCapabilities one = builder1.build();
@@ -103,6 +119,7 @@
     public void testParcelable() {
         TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
         assertRoundTripParcelable(builder.build());
@@ -110,6 +127,9 @@
         builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
         assertRoundTripParcelable(builder.build());
 
+        builder.setUseLocationEnabled(false);
+        assertRoundTripParcelable(builder.build());
+
         builder.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
         assertRoundTripParcelable(builder.build());
 
@@ -126,6 +146,7 @@
                         .build();
         TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
                 .build();
@@ -149,6 +170,7 @@
                         .build();
         TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+                .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                 .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
                 .build();
@@ -164,6 +186,7 @@
     public void copyBuilder_copiesAllFields() {
         TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+                .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                 .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
                 .build();
@@ -176,6 +199,24 @@
             TimeZoneCapabilities expectedCapabilities =
                     new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                             .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+                            .setUseLocationEnabled(true)
+                            .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+                            .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                            .build();
+
+            assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
+        }
+
+        {
+            TimeZoneCapabilities updatedCapabilities =
+                    new TimeZoneCapabilities.Builder(capabilities)
+                            .setUseLocationEnabled(false)
+                            .build();
+
+            TimeZoneCapabilities expectedCapabilities =
+                    new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+                            .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+                            .setUseLocationEnabled(false)
                             .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                             .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
                             .build();
@@ -192,6 +233,7 @@
             TimeZoneCapabilities expectedCapabilities =
                     new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                             .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+                            .setUseLocationEnabled(true)
                             .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                             .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
                             .build();
@@ -208,6 +250,7 @@
             TimeZoneCapabilities expectedCapabilities =
                     new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                             .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+                            .setUseLocationEnabled(true)
                             .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                             .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
                             .build();
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
index e82b699..2bce747 100755
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
@@ -45,54 +45,63 @@
                                 .setCecSupported(isCec)
                                 .setMhlSupported(isMhl)
                                 .setArcSupported(isArcSupported)
-                                .setEarcSupported(isEarcSupported),
+                                .setEarcSupported(isEarcSupported)
+                                .build(),
                         new HdmiPortInfo.Builder(portId, portType, address)
                                 .setCecSupported(isCec)
                                 .setMhlSupported(isMhl)
                                 .setArcSupported(isArcSupported)
-                                .setEarcSupported(isEarcSupported))
+                                .setEarcSupported(isEarcSupported)
+                                .build())
                 .addEqualityGroup(
                         new HdmiPortInfo.Builder(portId + 1, portType, address)
                                 .setCecSupported(isCec)
                                 .setMhlSupported(isMhl)
                                 .setArcSupported(isArcSupported)
-                                .setEarcSupported(isEarcSupported))
+                                .setEarcSupported(isEarcSupported)
+                                .build())
                 .addEqualityGroup(
                         new HdmiPortInfo.Builder(portId, portType + 1, address)
                                 .setCecSupported(isCec)
                                 .setMhlSupported(isMhl)
                                 .setArcSupported(isArcSupported)
-                                .setEarcSupported(isEarcSupported))
+                                .setEarcSupported(isEarcSupported)
+                                .build())
                 .addEqualityGroup(
                         new HdmiPortInfo.Builder(portId, portType, address + 1)
                                 .setCecSupported(isCec)
                                 .setMhlSupported(isMhl)
                                 .setArcSupported(isArcSupported)
-                                .setEarcSupported(isEarcSupported))
+                                .setEarcSupported(isEarcSupported)
+                                .build())
                 .addEqualityGroup(
                         new HdmiPortInfo.Builder(portId, portType, address)
                                 .setCecSupported(!isCec)
                                 .setMhlSupported(isMhl)
                                 .setArcSupported(isArcSupported)
-                                .setEarcSupported(isEarcSupported))
+                                .setEarcSupported(isEarcSupported)
+                                .build())
                 .addEqualityGroup(
                         new HdmiPortInfo.Builder(portId, portType, address)
                                 .setCecSupported(isCec)
                                 .setMhlSupported(!isMhl)
                                 .setArcSupported(isArcSupported)
-                                .setEarcSupported(isEarcSupported))
+                                .setEarcSupported(isEarcSupported)
+                                .build())
                 .addEqualityGroup(
                         new HdmiPortInfo.Builder(portId, portType, address)
                                 .setCecSupported(isCec)
                                 .setMhlSupported(isMhl)
                                 .setArcSupported(!isArcSupported)
-                                .setEarcSupported(isEarcSupported))
+                                .setEarcSupported(isEarcSupported)
+                                .build())
                 .addEqualityGroup(
                         new HdmiPortInfo.Builder(portId, portType, address)
                                 .setCecSupported(isCec)
                                 .setMhlSupported(isMhl)
                                 .setArcSupported(isArcSupported)
-                                .setEarcSupported(!isEarcSupported))
+                                .setEarcSupported(!isEarcSupported)
+                                .build())
                 .testEquals();
     }
 }
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index d0c3e5f..f233c6e 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -36,6 +36,12 @@
 }
 
 prebuilt_etc {
+    name: "initial-package-stopped-states.xml",
+    sub_dir: "sysconfig",
+    src: "initial-package-stopped-states.xml",
+}
+
+prebuilt_etc {
     name: "preinstalled-packages-platform-overlays.xml",
     product_specific: true,
     sub_dir: "sysconfig",
diff --git a/data/etc/initial-package-stopped-states.xml b/data/etc/initial-package-stopped-states.xml
new file mode 100644
index 0000000..6bda2c0
--- /dev/null
+++ b/data/etc/initial-package-stopped-states.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+ -->
+
+<!--
+This XML defines an allowlist for packages that should not be scanned in a "stopped" state.
+When this feature is turned on (indicated by the config config_stopSystemPackagesByDefault in
+core/res/res/values/config.xml) packages on the system partition that are encountered by
+the PackageManagerService for the first time are scanned in the "stopped" state. This allowlist
+is also considered while creating new users on the device. Stopped state is not set during
+subsequent reboots.
+
+Example usage
+    1. <initial-package-state package="com.example.app" stopped="false"/>
+        Indicates that a system package - com.example.app's initial stopped state should not be set
+        by the Package Manager. By default, system apps are marked as stopped.
+    2. <initial-package-state package="com.example.app" stopped="true"/>
+        Indicates that a system package - com.example.app's initial state should be set by the
+        Package Manager to "stopped=true". It will have the same effect on the
+        package's stopped state even if this package was not included in the allow list.
+    3. <initial-package-state package="com.example.app"/>
+        Invalid usage.
+-->
+
+<config></config>
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 2830d7eff..4715045 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -801,25 +801,32 @@
             ));
 
             if (mSpec.isDevicePropertiesAttestationIncluded()) {
+                final String platformReportedBrand = TextUtils.isEmpty(Build.BRAND_FOR_ATTESTATION)
+                        ? Build.BRAND : Build.BRAND_FOR_ATTESTATION;
                 params.add(KeyStore2ParameterUtils.makeBytes(
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
-                        Build.BRAND.getBytes(StandardCharsets.UTF_8)
+                        platformReportedBrand.getBytes(StandardCharsets.UTF_8)
                 ));
                 params.add(KeyStore2ParameterUtils.makeBytes(
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
                         Build.DEVICE.getBytes(StandardCharsets.UTF_8)
                 ));
+                final String platformReportedProduct =
+                        TextUtils.isEmpty(Build.PRODUCT_FOR_ATTESTATION) ? Build.PRODUCT :
+                                Build.PRODUCT_FOR_ATTESTATION;
                 params.add(KeyStore2ParameterUtils.makeBytes(
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
-                        Build.PRODUCT.getBytes(StandardCharsets.UTF_8)
+                        platformReportedProduct.getBytes(StandardCharsets.UTF_8)
                 ));
                 params.add(KeyStore2ParameterUtils.makeBytes(
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
                         Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)
                 ));
+                final String platformReportedModel = TextUtils.isEmpty(Build.MODEL_FOR_ATTESTATION)
+                        ? Build.MODEL : Build.MODEL_FOR_ATTESTATION;
                 params.add(KeyStore2ParameterUtils.makeBytes(
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL,
-                        Build.MODEL.getBytes(StandardCharsets.UTF_8)
+                        platformReportedModel.getBytes(StandardCharsets.UTF_8)
                 ));
             }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 2534498..e24c228 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -126,7 +126,7 @@
     private Icon mIcon;
     private boolean mIsBubble;
     private boolean mIsTextChanged;
-    private boolean mIsClearable;
+    private boolean mIsDismissable;
     private boolean mShouldSuppressNotificationDot;
     private boolean mShouldSuppressNotificationList;
     private boolean mShouldSuppressPeek;
@@ -181,7 +181,7 @@
     @VisibleForTesting(visibility = PRIVATE)
     public Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo,
             final int desiredHeight, final int desiredHeightResId, @Nullable final String title,
-            int taskId, @Nullable final String locus, boolean isClearable, Executor mainExecutor,
+            int taskId, @Nullable final String locus, boolean isDismissable, Executor mainExecutor,
             final Bubbles.BubbleMetadataFlagListener listener) {
         Objects.requireNonNull(key);
         Objects.requireNonNull(shortcutInfo);
@@ -190,7 +190,7 @@
         mKey = key;
         mGroupKey = null;
         mLocusId = locus != null ? new LocusId(locus) : null;
-        mIsClearable = isClearable;
+        mIsDismissable = isDismissable;
         mFlags = 0;
         mUser = shortcutInfo.getUserHandle();
         mPackageName = shortcutInfo.getPackage();
@@ -248,8 +248,8 @@
     }
 
     @Hide
-    public boolean isClearable() {
-        return mIsClearable;
+    public boolean isDismissable() {
+        return mIsDismissable;
     }
 
     /**
@@ -533,7 +533,7 @@
             mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
         }
 
-        mIsClearable = entry.isClearable();
+        mIsDismissable = entry.isDismissable();
         mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
         mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
         mShouldSuppressPeek = entry.shouldSuppressPeek();
@@ -612,7 +612,7 @@
      * Whether this notification should be shown in the shade.
      */
     boolean showInShade() {
-        return !shouldSuppressNotification() || !mIsClearable;
+        return !shouldSuppressNotification() || !mIsDismissable;
     }
 
     /**
@@ -877,7 +877,7 @@
         pw.print("  desiredHeight: "); pw.println(getDesiredHeightString());
         pw.print("  suppressNotif: "); pw.println(shouldSuppressNotification());
         pw.print("  autoExpand:    "); pw.println(shouldAutoExpand());
-        pw.print("  isClearable:   "); pw.println(mIsClearable);
+        pw.print("  isDismissable: "); pw.println(mIsDismissable);
         pw.println("  bubbleMetadataFlagListener null: " + (mBubbleMetadataFlagListener == null));
         if (mExpandedView != null) {
             mExpandedView.dump(pw);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index e3aefa5..e37c785 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -110,7 +110,7 @@
                     b.title,
                     b.taskId,
                     b.locusId?.id,
-                    b.isClearable
+                    b.isDismissable
             )
         }
     }
@@ -206,7 +206,7 @@
                                 entity.title,
                                 entity.taskId,
                                 entity.locus,
-                                entity.isClearable,
+                                entity.isDismissable,
                                 mainExecutor,
                                 bubbleMetadataFlagListener
                         )
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
index 5f42826..afe19c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
@@ -38,18 +38,18 @@
     private StatusBarNotification mSbn;
     private Ranking mRanking;
 
-    private boolean mIsClearable;
+    private boolean mIsDismissable;
     private boolean mShouldSuppressNotificationDot;
     private boolean mShouldSuppressNotificationList;
     private boolean mShouldSuppressPeek;
 
     public BubbleEntry(@NonNull StatusBarNotification sbn,
-            Ranking ranking, boolean isClearable, boolean shouldSuppressNotificationDot,
+            Ranking ranking, boolean isDismissable, boolean shouldSuppressNotificationDot,
             boolean shouldSuppressNotificationList, boolean shouldSuppressPeek) {
         mSbn = sbn;
         mRanking = ranking;
 
-        mIsClearable = isClearable;
+        mIsDismissable = isDismissable;
         mShouldSuppressNotificationDot = shouldSuppressNotificationDot;
         mShouldSuppressNotificationList = shouldSuppressNotificationList;
         mShouldSuppressPeek = shouldSuppressPeek;
@@ -115,9 +115,9 @@
         return mRanking.canBubble();
     }
 
-    /** @return true if this notification is clearable. */
-    public boolean isClearable() {
-        return mIsClearable;
+    /** @return true if this notification can be dismissed. */
+    public boolean isDismissable() {
+        return mIsDismissable;
     }
 
     /** @return true if {@link Policy#SUPPRESSED_EFFECT_BADGE} set for this notification. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
index f3abc27..9b2e263 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
@@ -28,5 +28,5 @@
     val title: String? = null,
     val taskId: Int,
     val locus: String? = null,
-    val isClearable: Boolean = false
+    val isDismissable: Boolean = false
 )
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
index 14c053c..48d8ccf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
@@ -43,9 +43,7 @@
 private const val ATTR_TITLE = "t"
 private const val ATTR_TASK_ID = "tid"
 private const val ATTR_LOCUS = "l"
-
-// TODO rename it to dismissable to follow NotificationEntry namings
-private const val ATTR_CLEARABLE = "d"
+private const val ATTR_DISMISSABLE = "d"
 
 /**
  * Writes the bubbles in xml format into given output stream.
@@ -87,7 +85,7 @@
         bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) }
         serializer.attribute(null, ATTR_TASK_ID, bubble.taskId.toString())
         bubble.locus?.let { serializer.attribute(null, ATTR_LOCUS, it) }
-        serializer.attribute(null, ATTR_CLEARABLE, bubble.isClearable.toString())
+        serializer.attribute(null, ATTR_DISMISSABLE, bubble.isDismissable.toString())
         serializer.endTag(null, TAG_BUBBLE)
     } catch (e: IOException) {
         throw RuntimeException(e)
@@ -147,7 +145,7 @@
             parser.getAttributeWithName(ATTR_TITLE),
             parser.getAttributeWithName(ATTR_TASK_ID)?.toInt() ?: INVALID_TASK_ID,
             parser.getAttributeWithName(ATTR_LOCUS),
-            parser.getAttributeWithName(ATTR_CLEARABLE)?.toBoolean() ?: false
+            parser.getAttributeWithName(ATTR_DISMISSABLE)?.toBoolean() ?: false
     )
 }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
index c02e2d1..3bfbcd2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
@@ -35,7 +35,7 @@
 
     private val user0Bubbles = listOf(
             BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1,
-                    isClearable = true),
+                    isDismissable = true),
             BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2,
                     null),
             BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null,
@@ -44,7 +44,7 @@
 
     private val user1Bubbles = listOf(
             BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3,
-                    isClearable = true),
+                    isDismissable = true),
             BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4,
                     null),
             BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null,
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index ee1c2ec..8877678 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -194,8 +194,6 @@
     ATRACE_CALL();
 
     if (window) {
-        // Ensure the hint session is running here, away from any critical paths
-        mHintSessionWrapper.init();
         mNativeSurface = std::make_unique<ReliableSurface>(window);
         mNativeSurface->init();
         if (enableTimeout) {
@@ -1064,6 +1062,10 @@
     mSyncDelayDuration = duration;
 }
 
+void CanvasContext::startHintSession() {
+    mHintSessionWrapper.init();
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index a811670..b26c018 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -229,6 +229,8 @@
 
     void setSyncDelayDuration(nsecs_t duration);
 
+    void startHintSession();
+
 private:
     CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
                   IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline,
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 1e011c2..bd85b7769 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -45,8 +45,12 @@
     pid_t uiThreadId = pthread_gettid_np(pthread_self());
     pid_t renderThreadId = getRenderThreadTid();
     mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* {
-        return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory,
-                                     uiThreadId, renderThreadId);
+        CanvasContext* context = CanvasContext::create(mRenderThread, translucent, rootRenderNode,
+                                                       contextFactory, uiThreadId, renderThreadId);
+        if (context != nullptr) {
+            mRenderThread.queue().post([=] { context->startHintSession(); });
+        }
+        return context;
     });
     mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
 }
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 1022d93..28496f1 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -1086,8 +1086,16 @@
         }
 
         /**
-         * Sets the visibility of this route to public. This is the default
-         * visibility for routes that are public to all other apps.
+         * Sets the visibility of this route to public.
+         *
+         * <p>By default, unless you call {@link #setVisibilityRestricted}, the new route will be
+         * public.
+         *
+         * <p>Public routes are visible to any application with a matching {@link
+         * RouteDiscoveryPreference#getPreferredFeatures feature}.
+         *
+         * <p>Calls to this method override previous calls to {@link #setVisibilityPublic} and
+         * {@link #setVisibilityRestricted}.
          */
         @NonNull
         public Builder setVisibilityPublic() {
@@ -1097,8 +1105,16 @@
         }
 
         /**
-         * Sets the visibility of this route to restricted. This means that the
-         * route is only visible to a set of package name.
+         * Sets the visibility of this route to restricted.
+         *
+         * <p>Routes with restricted visibility are only visible to its publisher application and
+         * applications whose package name is included in the provided {@code allowedPackages} set
+         * with a matching {@link RouteDiscoveryPreference#getPreferredFeatures feature}.
+         *
+         * <p>Calls to this method override previous calls to {@link #setVisibilityPublic} and
+         * {@link #setVisibilityRestricted}.
+         *
+         * @see #setVisibilityPublic
          * @param allowedPackages set of package names which are allowed to see this route.
          */
         @NonNull
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index ff0a492..96117ef 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -17,7 +17,6 @@
 package android.media;
 
 import android.annotation.IntDef;
-import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -45,14 +44,24 @@
 public final class RouteListingPreference implements Parcelable {
 
     /**
-     * {@link Intent} action for apps to take the user to a screen for transferring media playback
-     * to the route with the id provided by the extra with key {@link #EXTRA_ROUTE_ID}.
+     * {@link Intent} action that the system uses to take the user the app when the user selects an
+     * {@link Item} whose {@link Item#getSelectionBehavior() selection behavior} is {@link
+     * Item#SELECTION_BEHAVIOR_GO_TO_APP}.
+     *
+     * <p>The launched intent will identify the selected item using the extra identified by {@link
+     * #EXTRA_ROUTE_ID}.
+     *
+     * @see #getLinkedItemComponentName()
+     * @see Item#SELECTION_BEHAVIOR_GO_TO_APP
      */
     public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
 
     /**
      * {@link Intent} string extra key that contains the {@link Item#getRouteId() id} of the route
      * to transfer to, as part of an {@link #ACTION_TRANSFER_MEDIA} intent.
+     *
+     * @see #getLinkedItemComponentName()
+     * @see Item#SELECTION_BEHAVIOR_GO_TO_APP
      */
     public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
 
@@ -72,12 +81,12 @@
 
     @NonNull private final List<Item> mItems;
     private final boolean mUseSystemOrdering;
-    @Nullable private final ComponentName mInAppOnlyItemRoutingReceiver;
+    @Nullable private final ComponentName mLinkedItemComponentName;
 
     private RouteListingPreference(Builder builder) {
         mItems = builder.mItems;
         mUseSystemOrdering = builder.mUseSystemOrdering;
-        mInAppOnlyItemRoutingReceiver = builder.mInAppOnlyItemRoutingReceiver;
+        mLinkedItemComponentName = builder.mLinkedItemComponentName;
     }
 
     private RouteListingPreference(Parcel in) {
@@ -85,7 +94,7 @@
                 in.readParcelableList(new ArrayList<>(), Item.class.getClassLoader(), Item.class);
         mItems = List.copyOf(items);
         mUseSystemOrdering = in.readBoolean();
-        mInAppOnlyItemRoutingReceiver = ComponentName.readFromParcel(in);
+        mLinkedItemComponentName = ComponentName.readFromParcel(in);
     }
 
     /**
@@ -110,18 +119,20 @@
     }
 
     /**
-     * Returns a {@link ComponentName} for handling routes disabled via {@link
-     * Item#DISABLE_REASON_IN_APP_ONLY}, or null if the user needs to manually navigate to the app
-     * in order to route to select the corresponding routes.
+     * Returns a {@link ComponentName} for navigating to the application.
      *
-     * <p>If the user selects an {@link Item} disabled via {@link Item#DISABLE_REASON_IN_APP_ONLY},
-     * and this method returns a non-null {@link ComponentName}, the system takes the user back to
-     * the app by launching an intent to the returned {@link ComponentName}, using action {@link
-     * #ACTION_TRANSFER_MEDIA}, with the extra {@link #EXTRA_ROUTE_ID}.
+     * <p>Must not be null if any of the {@link #getItems() items} of this route listing preference
+     * has {@link Item#getSelectionBehavior() selection behavior} {@link
+     * Item#SELECTION_BEHAVIOR_GO_TO_APP}.
+     *
+     * <p>The system navigates to the application when the user selects {@link Item} with {@link
+     * Item#SELECTION_BEHAVIOR_GO_TO_APP} by launching an intent to the returned {@link
+     * ComponentName}, using action {@link #ACTION_TRANSFER_MEDIA}, with the extra {@link
+     * #EXTRA_ROUTE_ID}.
      */
     @Nullable
-    public ComponentName getInAppOnlyItemRoutingReceiver() {
-        return mInAppOnlyItemRoutingReceiver;
+    public ComponentName getLinkedItemComponentName() {
+        return mLinkedItemComponentName;
     }
 
     // RouteListingPreference Parcelable implementation.
@@ -135,7 +146,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelableList(mItems, flags);
         dest.writeBoolean(mUseSystemOrdering);
-        ComponentName.writeToParcel(mInAppOnlyItemRoutingReceiver, dest);
+        ComponentName.writeToParcel(mLinkedItemComponentName, dest);
     }
 
     // Equals and hashCode.
@@ -151,13 +162,12 @@
         RouteListingPreference that = (RouteListingPreference) other;
         return mItems.equals(that.mItems)
                 && mUseSystemOrdering == that.mUseSystemOrdering
-                && Objects.equals(
-                        mInAppOnlyItemRoutingReceiver, that.mInAppOnlyItemRoutingReceiver);
+                && Objects.equals(mLinkedItemComponentName, that.mLinkedItemComponentName);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mItems, mUseSystemOrdering, mInAppOnlyItemRoutingReceiver);
+        return Objects.hash(mItems, mUseSystemOrdering, mLinkedItemComponentName);
     }
 
     /** Builder for {@link RouteListingPreference}. */
@@ -165,7 +175,7 @@
 
         private List<Item> mItems;
         private boolean mUseSystemOrdering;
-        private ComponentName mInAppOnlyItemRoutingReceiver;
+        private ComponentName mLinkedItemComponentName;
 
         /** Creates a new instance with default values (documented in the setters). */
         public Builder() {
@@ -198,14 +208,13 @@
         }
 
         /**
-         * See {@link #getInAppOnlyItemRoutingReceiver()}.
+         * See {@link #getLinkedItemComponentName()}.
          *
          * <p>The default value is {@code null}.
          */
         @NonNull
-        public Builder setInAppOnlyItemRoutingReceiver(
-                @Nullable ComponentName inAppOnlyItemRoutingReceiver) {
-            mInAppOnlyItemRoutingReceiver = inAppOnlyItemRoutingReceiver;
+        public Builder setLinkedItemComponentName(@Nullable ComponentName linkedItemComponentName) {
+            mLinkedItemComponentName = linkedItemComponentName;
             return this;
         }
 
@@ -225,6 +234,29 @@
         /** @hide */
         @Retention(RetentionPolicy.SOURCE)
         @IntDef(
+                prefix = {"SELECTION_BEHAVIOR_"},
+                value = {
+                    SELECTION_BEHAVIOR_NONE,
+                    SELECTION_BEHAVIOR_TRANSFER,
+                    SELECTION_BEHAVIOR_GO_TO_APP
+                })
+        public @interface SelectionBehavior {}
+
+        /** The corresponding route is not selectable by the user. */
+        public static final int SELECTION_BEHAVIOR_NONE = 0;
+        /** If the user selects the corresponding route, the media transfers to the said route. */
+        public static final int SELECTION_BEHAVIOR_TRANSFER = 1;
+        /**
+         * If the user selects the corresponding route, the system takes the user to the
+         * application.
+         *
+         * <p>The system uses {@link #getLinkedItemComponentName()} in order to navigate to the app.
+         */
+        public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(
                 flag = true,
                 prefix = {"FLAG_"},
                 value = {FLAG_ONGOING_SESSION, FLAG_SUGGESTED_ROUTE})
@@ -251,46 +283,40 @@
         @IntDef(
                 prefix = {"DISABLE_REASON_"},
                 value = {
-                    DISABLE_REASON_NONE,
-                    DISABLE_REASON_SUBSCRIPTION_REQUIRED,
-                    DISABLE_REASON_DOWNLOADED_CONTENT,
-                    DISABLE_REASON_AD,
-                    DISABLE_REASON_IN_APP_ONLY,
-                    DISABLE_REASON_CUSTOM
+                    SUBTEXT_NONE,
+                    SUBTEXT_SUBSCRIPTION_REQUIRED,
+                    SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED,
+                    SUBTEXT_AD_ROUTING_DISALLOWED,
+                    SUBTEXT_CUSTOM
                 })
-        public @interface DisableReason {}
+        public @interface SubText {}
 
-        /** The corresponding route is available for routing. */
-        public static final int DISABLE_REASON_NONE = 0;
+        /** The corresponding route has no associated subtext. */
+        public static final int SUBTEXT_NONE = 0;
         /**
-         * The corresponding route requires a special subscription in order to be available for
-         * routing.
+         * The corresponding route's subtext must indicate that it requires a special subscription
+         * in order to be available for routing.
          */
-        public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1;
+        public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 1;
         /**
-         * The corresponding route is not available because downloaded content cannot be routed to
-         * it.
+         * The corresponding route's subtext must indicate that downloaded content cannot be routed
+         * to it.
          */
-        public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2;
-        /** The corresponding route is not available because an ad is in progress. */
-        public static final int DISABLE_REASON_AD = 3;
+        public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 2;
         /**
-         * The corresponding route is only available for routing from within the app.
-         *
-         * <p>The user may still select the corresponding route if the app provides an {@link
-         * #getInAppOnlyItemRoutingReceiver() in-app routing receiver}, in which case the system
-         * will take the user to the app.
+         * The corresponding route's subtext must indicate that it is not available because an ad is
+         * in progress.
          */
-        public static final int DISABLE_REASON_IN_APP_ONLY = 4;
+        public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 3;
         /**
-         * The corresponding route is not available because of the reason described by {@link
-         * #getCustomDisableReasonMessage()}.
+         * The corresponding route's subtext must be obtained from {@link
+         * #getCustomSubtextMessage()}.
          *
          * <p>Applications should strongly prefer one of the other disable reasons (for the full
-         * list, see {@link #getDisableReason()}) in order to guarantee correct localization and
-         * rendering across all form factors.
+         * list, see {@link #getSubText()}) in order to guarantee correct localization and rendering
+         * across all form factors.
          */
-        public static final int DISABLE_REASON_CUSTOM = 5;
+        public static final int SUBTEXT_CUSTOM = 10000;
 
         @NonNull
         public static final Creator<Item> CREATOR =
@@ -307,29 +333,27 @@
                 };
 
         @NonNull private final String mRouteId;
+        @SelectionBehavior private final int mSelectionBehavior;
         @Flags private final int mFlags;
-        @DisableReason private final int mDisableReason;
-        private final int mSessionParticipantCount;
-        @Nullable private final CharSequence mCustomDisableReasonMessage;
+        @SubText private final int mSubText;
+
+        @Nullable private final CharSequence mCustomSubtextMessage;
 
         private Item(@NonNull Builder builder) {
             mRouteId = builder.mRouteId;
+            mSelectionBehavior = builder.mSelectionBehavior;
             mFlags = builder.mFlags;
-            mDisableReason = builder.mDisableReason;
-            mSessionParticipantCount = builder.mSessionParticipantCount;
-            mCustomDisableReasonMessage = builder.mCustomDisableReasonMessage;
-            validateCustomDisableReasonMessage();
+            mSubText = builder.mSubText;
+            mCustomSubtextMessage = builder.mCustomSubtextMessage;
         }
 
         private Item(Parcel in) {
             mRouteId = in.readString();
             Preconditions.checkArgument(!TextUtils.isEmpty(mRouteId));
+            mSelectionBehavior = in.readInt();
             mFlags = in.readInt();
-            mDisableReason = in.readInt();
-            mSessionParticipantCount = in.readInt();
-            Preconditions.checkArgument(mSessionParticipantCount >= 0);
-            mCustomDisableReasonMessage = in.readCharSequence();
-            validateCustomDisableReasonMessage();
+            mSubText = in.readInt();
+            mCustomSubtextMessage = in.readCharSequence();
         }
 
         /**
@@ -343,6 +367,17 @@
         }
 
         /**
+         * Returns the behavior that the corresponding route has if the user selects it.
+         *
+         * @see #SELECTION_BEHAVIOR_NONE
+         * @see #SELECTION_BEHAVIOR_TRANSFER
+         * @see #SELECTION_BEHAVIOR_GO_TO_APP
+         */
+        public int getSelectionBehavior() {
+            return mSelectionBehavior;
+        }
+
+        /**
          * Returns the flags associated to the route that corresponds to this item.
          *
          * @see #FLAG_ONGOING_SESSION
@@ -354,49 +389,42 @@
         }
 
         /**
-         * Returns the reason for the corresponding route to be disabled, or {@link
-         * #DISABLE_REASON_NONE} if the route is not disabled.
+         * Returns the type of subtext associated to this route.
          *
-         * @see #DISABLE_REASON_NONE
-         * @see #DISABLE_REASON_SUBSCRIPTION_REQUIRED
-         * @see #DISABLE_REASON_DOWNLOADED_CONTENT
-         * @see #DISABLE_REASON_AD
-         * @see #DISABLE_REASON_IN_APP_ONLY
-         * @see #DISABLE_REASON_CUSTOM
+         * <p>Subtext types other than {@link #SUBTEXT_NONE} and {@link #SUBTEXT_CUSTOM} must not
+         * have {@link #SELECTION_BEHAVIOR_TRANSFER}.
+         *
+         * <p>If this method returns {@link #SUBTEXT_CUSTOM}, then the subtext is obtained form
+         * {@link #getCustomSubtextMessage()}.
+         *
+         * @see #SUBTEXT_NONE
+         * @see #SUBTEXT_SUBSCRIPTION_REQUIRED
+         * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED
+         * @see #SUBTEXT_AD_ROUTING_DISALLOWED
+         * @see #SUBTEXT_CUSTOM
          */
-        @DisableReason
-        public int getDisableReason() {
-            return mDisableReason;
+        @SubText
+        public int getSubText() {
+            return mSubText;
         }
 
         /**
-         * Returns a non-negative number of participants in the ongoing session (if any) on the
-         * corresponding route.
+         * Returns a human-readable {@link CharSequence} providing the subtext for the corresponding
+         * route.
          *
-         * <p>The system ignores this value if zero, or if {@link #getFlags()} does not include
-         * {@link #FLAG_ONGOING_SESSION}.
-         */
-        public int getSessionParticipantCount() {
-            return mSessionParticipantCount;
-        }
-
-        /**
-         * Returns a human-readable {@link CharSequence} describing the reason for this route to be
-         * disabled. May be null if {@link #getDisableReason()} is not {@link
-         * #DISABLE_REASON_CUSTOM}.
-         *
-         * <p>This value is ignored if the {@link #getDisableReason() disable reason} for this item
-         * is not {@link #DISABLE_REASON_CUSTOM}.
+         * <p>This value is ignored if the {@link #getSubText() subtext} for this item is not {@link
+         * #SUBTEXT_CUSTOM}..
          *
          * <p>Applications must provide a localized message that matches the system's locale. See
          * {@link Locale#getDefault()}.
          *
-         * <p>This message is a hint for the system. Applications should strongly prefer one of the
-         * other disable reasons listed in {@link #getDisableReason()}.
+         * <p>Applications should avoid using custom messages (and instead use one of non-custom
+         * subtexts listed in {@link #getSubText()} in order to guarantee correct visual
+         * representation and localization on all form factors.
          */
         @Nullable
-        public CharSequence getCustomDisableReasonMessage() {
-            return mCustomDisableReasonMessage;
+        public CharSequence getCustomSubtextMessage() {
+            return mCustomSubtextMessage;
         }
 
         // Item Parcelable implementation.
@@ -409,10 +437,10 @@
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeString(mRouteId);
+            dest.writeInt(mSelectionBehavior);
             dest.writeInt(mFlags);
-            dest.writeInt(mDisableReason);
-            dest.writeInt(mSessionParticipantCount);
-            dest.writeCharSequence(mCustomDisableReasonMessage);
+            dest.writeInt(mSubText);
+            dest.writeCharSequence(mCustomSubtextMessage);
         }
 
         // Equals and hashCode.
@@ -427,40 +455,26 @@
             }
             Item item = (Item) other;
             return mRouteId.equals(item.mRouteId)
+                    && mSelectionBehavior == item.mSelectionBehavior
                     && mFlags == item.mFlags
-                    && mDisableReason == item.mDisableReason
-                    && mSessionParticipantCount == item.mSessionParticipantCount
-                    && TextUtils.equals(
-                            mCustomDisableReasonMessage, item.mCustomDisableReasonMessage);
+                    && mSubText == item.mSubText
+                    && TextUtils.equals(mCustomSubtextMessage, item.mCustomSubtextMessage);
         }
 
         @Override
         public int hashCode() {
             return Objects.hash(
-                    mRouteId,
-                    mFlags,
-                    mDisableReason,
-                    mSessionParticipantCount,
-                    mCustomDisableReasonMessage);
-        }
-
-        private void validateCustomDisableReasonMessage() {
-            if (mDisableReason == DISABLE_REASON_CUSTOM) {
-                Preconditions.checkArgument(
-                        !TextUtils.isEmpty(mCustomDisableReasonMessage),
-                        "customDisableReasonMessage must not be null or empty if disable reason is"
-                                + " DISABLE_REASON_CUSTOM.");
-            }
+                    mRouteId, mSelectionBehavior, mFlags, mSubText, mCustomSubtextMessage);
         }
 
         /** Builder for {@link Item}. */
         public static final class Builder {
 
             private final String mRouteId;
+            private int mSelectionBehavior;
             private int mFlags;
-            private int mDisableReason;
-            private int mSessionParticipantCount;
-            private CharSequence mCustomDisableReasonMessage;
+            private int mSubText;
+            private CharSequence mCustomSubtextMessage;
 
             /**
              * Constructor.
@@ -470,39 +484,51 @@
             public Builder(@NonNull String routeId) {
                 Preconditions.checkArgument(!TextUtils.isEmpty(routeId));
                 mRouteId = routeId;
-                mDisableReason = DISABLE_REASON_NONE;
+                mSelectionBehavior = SELECTION_BEHAVIOR_TRANSFER;
+                mSubText = SUBTEXT_NONE;
             }
 
-            /** See {@link Item#getFlags()}. */
+            /**
+             * See {@link Item#getSelectionBehavior()}.
+             *
+             * <p>The default value is {@link #ACTION_TRANSFER_MEDIA}.
+             */
+            @NonNull
+            public Builder setSelectionBehavior(int selectionBehavior) {
+                mSelectionBehavior = selectionBehavior;
+                return this;
+            }
+
+            /**
+             * See {@link Item#getFlags()}.
+             *
+             * <p>The default value is zero (no flags).
+             */
             @NonNull
             public Builder setFlags(int flags) {
                 mFlags = flags;
                 return this;
             }
 
-            /** See {@link Item#getDisableReason()}. */
+            /**
+             * See {@link Item#getSubText()}.
+             *
+             * <p>The default value is {@link #SUBTEXT_NONE}.
+             */
             @NonNull
-            public Builder setDisableReason(int disableReason) {
-                mDisableReason = disableReason;
+            public Builder setSubText(int subText) {
+                mSubText = subText;
                 return this;
             }
 
-            /** See {@link Item#getSessionParticipantCount()}. */
+            /**
+             * See {@link Item#getCustomSubtextMessage()}.
+             *
+             * <p>The default value is {@code null}.
+             */
             @NonNull
-            public Builder setSessionParticipantCount(
-                    @IntRange(from = 0) int sessionParticipantCount) {
-                Preconditions.checkArgument(
-                        sessionParticipantCount >= 0,
-                        "sessionParticipantCount must be non-negative.");
-                mSessionParticipantCount = sessionParticipantCount;
-                return this;
-            }
-
-            /** See {@link Item#getCustomDisableReasonMessage()}. */
-            @NonNull
-            public Builder setCustomDisableReasonMessage(
-                    @Nullable CharSequence customDisableReasonMessage) {
-                mCustomDisableReasonMessage = customDisableReasonMessage;
+            public Builder setCustomSubtextMessage(@Nullable CharSequence customSubtextMessage) {
+                mCustomSubtextMessage = customSubtextMessage;
                 return this;
             }
 
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 59e19ed..1a3e54d 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -25,6 +25,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.media.AudioFormat;
@@ -50,6 +51,7 @@
  * VoiceInteractionService} instead. Access to this class is protected by a permission
  * granted only to system or privileged apps.
  * @deprecated use {@link SoundTriggerManager} directly
+ *
  * @hide
  */
 @Deprecated
@@ -67,7 +69,7 @@
     private final Object mLock = new Object();
 
     private final ISoundTriggerSession mSoundTriggerSession;
-    private final UUID mSoundModelId;
+    private final GenericSoundModel mSoundModel;
     private final Callback mCallback;
     private final Handler mHandler;
     private final RecognitionCallback mRecognitionCallback;
@@ -277,10 +279,11 @@
      * This class should be constructed by the {@link SoundTriggerManager}.
      * @hide
      */
-    SoundTriggerDetector(ISoundTriggerSession soundTriggerSession, UUID soundModelId,
+    SoundTriggerDetector(ISoundTriggerSession soundTriggerSession,
+            @NonNull GenericSoundModel soundModel,
             @NonNull Callback callback, @Nullable Handler handler) {
         mSoundTriggerSession = soundTriggerSession;
-        mSoundModelId = soundModelId;
+        mSoundModel = soundModel;
         mCallback = callback;
         if (handler == null) {
             mHandler = new MyHandler();
@@ -320,7 +323,7 @@
 
         int status;
         try {
-            status = mSoundTriggerSession.startRecognition(new ParcelUuid(mSoundModelId),
+            status = mSoundTriggerSession.startRecognition(mSoundModel,
                     mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
                             allowMultipleTriggers, null, null, audioCapabilities),
                     runInBatterySaver);
@@ -339,7 +342,7 @@
     public boolean stopRecognition() {
         int status = STATUS_OK;
         try {
-            status = mSoundTriggerSession.stopRecognition(new ParcelUuid(mSoundModelId),
+            status = mSoundTriggerSession.stopRecognition(new ParcelUuid(mSoundModel.getUuid()),
                     mRecognitionCallback);
         } catch (RemoteException e) {
             return false;
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 8dc708a..c473a01 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -184,11 +184,16 @@
         if (oldInstance != null) {
             // Shutdown old instance.
         }
-        SoundTriggerDetector newInstance = new SoundTriggerDetector(mSoundTriggerSession,
-                soundModelId, callback, handler);
-        mReceiverInstanceMap.put(soundModelId, newInstance);
-        return newInstance;
-    }
+        try {
+            SoundTriggerDetector newInstance = new SoundTriggerDetector(mSoundTriggerSession,
+                    mSoundTriggerSession.getSoundModel(new ParcelUuid(soundModelId)),
+                    callback, handler);
+            mReceiverInstanceMap.put(soundModelId, newInstance);
+            return newInstance;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+   }
 
     /**
      * Class captures the data and fields that represent a non-keyphrase sound model. Use the
diff --git a/media/java/android/media/tv/tuner/frontend/IptvFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettings.java
index fb11ea9..ba75102 100644
--- a/media/java/android/media/tv/tuner/frontend/IptvFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettings.java
@@ -92,7 +92,7 @@
     private final long mBitrate;
     private final String mContentUrl;
 
-    public IptvFrontendSettings(@NonNull byte[] srcIpAddress, @NonNull byte[] dstIpAddress,
+    private IptvFrontendSettings(@NonNull byte[] srcIpAddress, @NonNull byte[] dstIpAddress,
             int srcPort, int dstPort, @NonNull IptvFrontendSettingsFec fec, int protocol, int igmp,
             long bitrate, @NonNull String contentUrl) {
         super(0);
diff --git a/media/java/android/media/tv/tuner/frontend/IptvFrontendSettingsFec.java b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettingsFec.java
index 699d615..a70af17 100644
--- a/media/java/android/media/tv/tuner/frontend/IptvFrontendSettingsFec.java
+++ b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettingsFec.java
@@ -62,7 +62,7 @@
     private final int mFecRowNum;
     private final int mFecColNum;
 
-    public IptvFrontendSettingsFec(@FecType int fecType, int fecRowNum, int fecColNum) {
+    private IptvFrontendSettingsFec(@FecType int fecType, int fecRowNum, int fecColNum) {
         mFecType = fecType;
         mFecRowNum = fecRowNum;
         mFecColNum = fecColNum;
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index ae6f71c..ab680b0 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -38,10 +38,11 @@
     <!-- Message for updating an existing app [CHAR LIMIT=NONE] -->
     <string name="install_confirm_question_update">Do you want to update this app?</string>
     <!-- TODO(b/244413073) Revise the description after getting UX input and UXR on this. -->
-    <!-- Message for updating an existing app when updating owner changed [CHAR LIMIT=NONE] -->
+    <!-- Message for updating an existing app when updating owner changed [DO NOT TRANSLATE][CHAR LIMIT=NONE] -->
     <string name="install_confirm_question_update_owner_changed">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nBy updating, you\'ll get future updates from <xliff:g id="new_update_owner">%2$s</xliff:g> instead.</string>
-    <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
-    <string name="install_confirm_question_update_owner_reminder">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nDo you want to install this update from <xliff:g id="new_update_owner">%2$s</xliff:g>.</string>
+    <!-- TODO(b/244413073) Revise the description after getting UX input and UXR on this. -->
+    <!-- Message for updating an existing app with update owner reminder [DO NOT TRANSLATE][CHAR LIMIT=NONE] -->
+    <string name="install_confirm_question_update_owner_reminder">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nDo you want to install this update from <xliff:g id="new_update_owner">%2$s</xliff:g>?</string>
     <!-- [CHAR LIMIT=100] -->
     <string name="install_failed">App not installed.</string>
     <!-- Reason displayed when installation fails because the package was blocked
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
index 72f662f..078c925 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
@@ -175,6 +175,7 @@
         RegularScaffold(title = "Page - ${page.debugBrief()}") {
             Text(text = "id = ${page.id}")
             Text(text = page.debugArguments())
+            Text(text = "enabled = ${page.isEnabled()}")
             Text(text = "Entry size: ${pageWithEntry.entries.size}")
             Preference(model = object : PreferenceModel {
                 override val title = "open page"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
index 6ecf9c3..d95ed05 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
@@ -66,6 +66,11 @@
         "linkFrom = ${fromPage?.debugBrief()} ${fromPage?.debugArguments()}",
         "linkTo = ${toPage?.debugBrief()} ${toPage?.debugArguments()}",
         "hierarchy_path = $entryPathWithName",
+        "------ ATTRIBUTION ------",
+        "allowSearch = $isAllowSearch",
+        "isSearchDynamic = $isSearchDataDynamic",
+        "isSearchMutable = $hasMutableStatus",
+        "hasSlice = $hasSliceSupport",
         "------ SEARCH ------",
         "search_path = $entryPathWithTitle",
         searchData?.debugContent() ?: "no search data",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 4d8b89b..a8432d6 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -32,6 +32,7 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.unit.IntOffset
 import androidx.core.view.WindowCompat
@@ -42,6 +43,7 @@
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.AnimatedNavHost
 import com.android.settingslib.spa.framework.compose.LocalNavController
 import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
@@ -136,7 +138,12 @@
                         AnimatedContentScope.SlideDirection.Right, animationSpec = slideEffect
                     ) + fadeOut(animationSpec = fadeEffect)
                 },
-            ) { navBackStackEntry -> spp.PageWithEvent(navBackStackEntry.arguments) }
+            ) { navBackStackEntry ->
+                val page = remember(navBackStackEntry.arguments) {
+                    spp.createSettingsPage(navBackStackEntry.arguments)
+                }
+                page.PageWithEvent()
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 2175e55..44714ab 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -198,6 +198,7 @@
             toPage = toPage,
 
             // attributes
+            // TODO: set isEnabled & (isAllowSearch, hasSliceSupport) separately
             isAllowSearch = isEnabled && isAllowSearch,
             isSearchDataDynamic = isSearchDataDynamic,
             hasMutableStatus = hasMutableStatus,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index a362877..8bbeb62 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.spa.framework.common
 
 import android.os.Bundle
+import androidx.compose.runtime.Composable
 import androidx.navigation.NamedNavArgument
 import com.android.settingslib.spa.framework.util.isRuntimeParam
 import com.android.settingslib.spa.framework.util.navLink
@@ -91,14 +92,22 @@
     }
 
     fun isBrowsable(): Boolean {
-        return !isCreateBy(NULL_PAGE_NAME) &&
-            !hasRuntimeParam()
+        return !isCreateBy(NULL_PAGE_NAME) && !hasRuntimeParam()
+    }
+
+    private fun getProvider(): SettingsPageProvider? {
+        if (!SpaEnvironmentFactory.isReady()) return null
+        val pageProviderRepository by SpaEnvironmentFactory.instance.pageProviderRepository
+        return pageProviderRepository.getProviderOrNull(sppName)
     }
 
     fun isEnabled(): Boolean {
-        if (!SpaEnvironmentFactory.isReady()) return false
-        val pageProviderRepository by SpaEnvironmentFactory.instance.pageProviderRepository
-        return pageProviderRepository.getProviderOrNull(sppName)?.isEnabled(arguments) ?: false
+        return getProvider()?.isEnabled(arguments) ?: false
+    }
+
+    @Composable
+    fun UiLayout() {
+        getProvider()?.Page(arguments)
     }
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index f672ee0..02962a5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -71,11 +71,15 @@
 
     val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) }
 
-    // In Robolectric test, applicationContext is not available. Use context as fallback.
+    // The application context. Use local context as fallback when applicationContext is not
+    // available (e.g. in Robolectric test).
     val appContext: Context = context.applicationContext ?: context
 
+    // Set your SpaLogger implementation, for any SPA events logging.
     open val logger: SpaLogger = object : SpaLogger {}
 
+    // Specify class name of browse activity and slice broadcast receiver, which is used to
+    // generate the necessary intents.
     open val browseActivityClass: Class<out Activity>? = null
     open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
index 22a4563..dde4e04 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -16,32 +16,27 @@
 
 package com.android.settingslib.spa.framework.util
 
-import android.os.Bundle
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
 import androidx.core.os.bundleOf
 import com.android.settingslib.spa.framework.common.LOG_DATA_DISPLAY_NAME
 import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME
 import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.LogEvent
 import com.android.settingslib.spa.framework.common.SettingsPage
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.LifecycleEffect
 import com.android.settingslib.spa.framework.compose.LocalNavController
 import com.android.settingslib.spa.framework.compose.NavControllerWrapper
 
 @Composable
-internal fun SettingsPageProvider.PageWithEvent(arguments: Bundle? = null) {
-    if (!isEnabled(arguments)) return
-    val page = remember(arguments) { createSettingsPage(arguments) }
+internal fun SettingsPage.PageWithEvent() {
+    if (!isEnabled()) return
     val navController = LocalNavController.current
     LifecycleEffect(
-        onStart = { page.logPageEvent(LogEvent.PAGE_ENTER, navController) },
-        onStop = { page.logPageEvent(LogEvent.PAGE_LEAVE, navController) },
+        onStart = { logPageEvent(LogEvent.PAGE_ENTER, navController) },
+        onStop = { logPageEvent(LogEvent.PAGE_LEAVE, navController) },
     )
-    Page(arguments)
+    UiLayout()
 }
 
 private fun SettingsPage.logPageEvent(event: LogEvent, navController: NavControllerWrapper) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index d6f5328..207c174 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -30,9 +30,9 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.DialogProperties
 
@@ -74,7 +74,6 @@
     return alertDialogPresenter
 }
 
-@OptIn(ExperimentalComposeUiApi::class)
 @Composable
 private fun AlertDialogPresenter.SettingsAlertDialog(
     confirmButton: AlertDialogButton?,
@@ -82,15 +81,9 @@
     title: String?,
     text: @Composable (() -> Unit)?,
 ) {
-    val configuration = LocalConfiguration.current
     AlertDialog(
         onDismissRequest = ::close,
-        modifier = when (configuration.orientation) {
-            Configuration.ORIENTATION_LANDSCAPE -> {
-                Modifier.width(configuration.screenWidthDp.dp * 0.6f)
-            }
-            else -> Modifier
-        },
+        modifier = Modifier.width(getDialogWidth()),
         confirmButton = { confirmButton?.let { Button(it) } },
         dismissButton = dismissButton?.let { { Button(it) } },
         title = title?.let { { Text(it) } },
@@ -106,6 +99,15 @@
 }
 
 @Composable
+private fun getDialogWidth(): Dp {
+    val configuration = LocalConfiguration.current
+    return configuration.screenWidthDp.dp * when (configuration.orientation) {
+        Configuration.ORIENTATION_LANDSCAPE -> 0.6f
+        else -> 0.8f
+    }
+}
+
+@Composable
 private fun AlertDialogPresenter.Button(button: AlertDialogButton) {
     TextButton(
         onClick = {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index d708e57..17ef283 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -30,8 +30,8 @@
 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
-import static android.media.RouteListingPreference.Item.DISABLE_REASON_NONE;
 import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE;
+import static android.media.RouteListingPreference.Item.SUBTEXT_NONE;
 
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
 
@@ -198,10 +198,11 @@
      *
      * @return disabled reason of device
      */
-    @RouteListingPreference.Item.DisableReason
+    @RouteListingPreference.Item.SubText
     public int getDisableReason() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
-                ? mItem.getDisableReason() : -1;
+                ? mItem.getSubText()
+                : -1;
     }
 
     /**
@@ -210,8 +211,9 @@
      * @return true if device has disabled reason
      */
     public boolean hasDisabledReason() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
-                && mItem.getDisableReason() != DISABLE_REASON_NONE;
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+                && mItem != null
+                && mItem.getSubText() != SUBTEXT_NONE;
     }
 
     /**
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java
new file mode 100644
index 0000000..bdd4869
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.settings.backup;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+/** Settings that should not be restored when target device is a large screen
+ *  i.e. tablets and foldables in unfolded state
+ */
+public class LargeScreenSettings {
+    private static final float LARGE_SCREEN_MIN_DPS = 600;
+    private static final String LARGE_SCREEN_DO_NOT_RESTORE = "accelerometer_rotation";
+
+   /**
+    * Autorotation setting should not be restored when the target device is a large screen.
+    * (b/243489549)
+    */
+    public static boolean doNotRestoreIfLargeScreenSetting(String key, Context context) {
+        return isLargeScreen(context) && LARGE_SCREEN_DO_NOT_RESTORE.equals(key);
+    }
+
+    // copied from systemui/shared/...Utilities.java
+    // since we don't want to add compile time dependency on sys ui package
+    private static boolean isLargeScreen(Context context) {
+        final WindowManager windowManager = context.getSystemService(WindowManager.class);
+        final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
+        float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
+                context.getResources().getConfiguration().densityDpi);
+        return smallestWidth >= LARGE_SCREEN_MIN_DPS;
+    }
+
+    private static float dpiFromPx(float size, int densityDpi) {
+        float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+        return (size / densityRatio);
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 6aa08f2..574fd5a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -37,6 +37,7 @@
 import android.provider.Settings;
 import android.provider.settings.backup.DeviceSpecificSettings;
 import android.provider.settings.backup.GlobalSettings;
+import android.provider.settings.backup.LargeScreenSettings;
 import android.provider.settings.backup.SecureSettings;
 import android.provider.settings.backup.SystemSettings;
 import android.provider.settings.validators.GlobalSettingsValidators;
@@ -812,6 +813,12 @@
                 continue;
             }
 
+            if (LargeScreenSettings.doNotRestoreIfLargeScreenSetting(key, getBaseContext())) {
+                Log.i(TAG, "Skipping restore for setting " + key + " as the target device "
+                        + "is a large screen (i.e tablet or foldable in unfolded state)");
+                continue;
+            }
+
             String value = null;
             boolean hasValueToRestore = false;
             if (cachedEntries.indexOfKey(key) >= 0) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 25b9906..7aeba95 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -238,6 +238,15 @@
         dumpSetting(s, p,
                 Settings.Global.AUDIO_SAFE_VOLUME_STATE,
                 GlobalSettingsProto.AUDIO_SAFE_VOLUME_STATE);
+        dumpSetting(s, p,
+                Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE,
+                GlobalSettingsProto.AUDIO_SAFE_CSD_CURRENT_VALUE);
+        dumpSetting(s, p,
+                Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING,
+                GlobalSettingsProto.AUDIO_SAFE_CSD_NEXT_WARNING);
+        dumpSetting(s, p,
+                Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS,
+                GlobalSettingsProto.AUDIO_SAFE_CSD_DOSE_RECORDS);
 
         final long autofillToken = p.start(GlobalSettingsProto.AUTOFILL);
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 3782ce4..f1ea482 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -131,6 +131,9 @@
                     Settings.Global.ART_VERIFIER_VERIFY_DEBUGGABLE,
                     Settings.Global.ASSISTED_GPS_ENABLED,
                     Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+                    Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE,
+                    Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING,
+                    Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS,
                     Settings.Global.AUTOFILL_LOGGING_LEVEL,
                     Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE,
                     Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 628fb38..8e98d2d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -690,6 +690,7 @@
             <intent-filter>
                 <action android:name="android.telecom.action.SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG" />
                 <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="tel" />
             </intent-filter>
         </activity>
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index 0b1a3e2..46c604b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -25,6 +25,7 @@
         "androidx.coordinatorlayout_coordinatorlayout",
         "androidx.core_core",
         "androidx.viewpager_viewpager",
+        "SettingsLib",
     ],
 
     uses_libs: [
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index 26748a9..0b4d7cd 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -16,6 +16,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     package="com.android.systemui.accessibility.accessibilitymenu">
+
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+
     <application>
         <service
             android:name="com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 76139c6..ecb2bf4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -22,19 +22,28 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManager;
+import android.media.AudioManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.view.Display;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.settingslib.display.BrightnessUtils;
+import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
 import com.android.systemui.accessibility.accessibilitymenu.view.A11yMenuOverlayLayout;
 
+import java.util.List;
+
 /** @hide */
 public class AccessibilityMenuService extends AccessibilityService
         implements View.OnTouchListener {
@@ -42,6 +51,11 @@
 
     private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
 
+    private static final int BRIGHTNESS_UP_INCREMENT_GAMMA =
+            (int) Math.ceil(BrightnessUtils.GAMMA_SPACE_MAX * 0.11f);
+    private static final int BRIGHTNESS_DOWN_INCREMENT_GAMMA =
+            (int) -Math.ceil(BrightnessUtils.GAMMA_SPACE_MAX * 0.11f);
+
     private long mLastTimeTouchedOutside = 0L;
     // Timeout used to ignore the A11y button onClick() when ACTION_OUTSIDE is also received on
     // clicking on the A11y button.
@@ -51,6 +65,8 @@
 
     private static boolean sInitialized = false;
 
+    private AudioManager mAudioManager;
+
     // TODO(b/136716947): Support multi-display once a11y framework side is ready.
     private DisplayManager mDisplayManager;
     final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
@@ -140,6 +156,7 @@
 
         mDisplayManager = getSystemService(DisplayManager.class);
         mDisplayManager.registerDisplayListener(mDisplayListener, null);
+        mAudioManager = getSystemService(AudioManager.class);
 
         sInitialized = true;
     }
@@ -170,9 +187,93 @@
      * @param view the shortcut button being clicked.
      */
     public void handleClick(View view) {
+        // Shortcuts are repeatable in a11y menu rather than unique, so use tag ID to handle.
+        int viewTag = (int) view.getTag();
+
+        if (viewTag == ShortcutId.ID_ASSISTANT_VALUE.ordinal()) {
+            // Always restart the voice command activity, so that the UI is reloaded.
+            startActivityIfIntentIsSafe(
+                    new Intent(Intent.ACTION_VOICE_COMMAND),
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        } else if (viewTag == ShortcutId.ID_A11YSETTING_VALUE.ordinal()) {
+            startActivityIfIntentIsSafe(
+                    new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS),
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        } else if (viewTag == ShortcutId.ID_POWER_VALUE.ordinal()) {
+            performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
+        } else if (viewTag == ShortcutId.ID_RECENT_VALUE.ordinal()) {
+            performGlobalAction(GLOBAL_ACTION_RECENTS);
+        } else if (viewTag == ShortcutId.ID_LOCKSCREEN_VALUE.ordinal()) {
+            performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
+        } else if (viewTag == ShortcutId.ID_QUICKSETTING_VALUE.ordinal()) {
+            performGlobalAction(GLOBAL_ACTION_QUICK_SETTINGS);
+        } else if (viewTag == ShortcutId.ID_NOTIFICATION_VALUE.ordinal()) {
+            performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS);
+        } else if (viewTag == ShortcutId.ID_SCREENSHOT_VALUE.ordinal()) {
+            performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT);
+        } else if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) {
+            adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA);
+            return;
+        } else if (viewTag == ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()) {
+            adjustBrightness(BRIGHTNESS_DOWN_INCREMENT_GAMMA);
+            return;
+        } else if (viewTag == ShortcutId.ID_VOLUME_UP_VALUE.ordinal()) {
+            adjustVolume(AudioManager.ADJUST_RAISE);
+            return;
+        } else if (viewTag == ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()) {
+            adjustVolume(AudioManager.ADJUST_LOWER);
+            return;
+        }
+
         mA11yMenuLayout.hideMenu();
     }
 
+    private void adjustBrightness(int increment) {
+        BrightnessInfo info = getDisplay().getBrightnessInfo();
+        int gamma = BrightnessUtils.convertLinearToGammaFloat(
+                info.brightness,
+                info.brightnessMinimum,
+                info.brightnessMaximum
+        );
+        gamma = Math.max(
+                BrightnessUtils.GAMMA_SPACE_MIN,
+                Math.min(BrightnessUtils.GAMMA_SPACE_MAX, gamma + increment));
+
+        float brightness = BrightnessUtils.convertGammaToLinearFloat(
+                gamma,
+                info.brightnessMinimum,
+                info.brightnessMaximum
+        );
+        mDisplayManager.setTemporaryBrightness(getDisplayId(), brightness);
+        mDisplayManager.setBrightness(getDisplayId(), brightness);
+        mA11yMenuLayout.showSnackbar(
+                getString(R.string.brightness_percentage_label,
+                        (gamma / (BrightnessUtils.GAMMA_SPACE_MAX / 100))));
+    }
+
+    private void adjustVolume(int direction) {
+        mAudioManager.adjustStreamVolume(
+                AudioManager.STREAM_MUSIC, direction,
+                AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+        final int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        final int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        mA11yMenuLayout.showSnackbar(
+                getString(
+                        R.string.music_volume_percentage_label,
+                        (int) (100.0 / maxVolume * volume))
+        );
+    }
+
+    private void startActivityIfIntentIsSafe(Intent intent, int flag) {
+        PackageManager packageManager = getPackageManager();
+        List<ResolveInfo> activities =
+                packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        if (!activities.isEmpty()) {
+            intent.setFlags(flag);
+            startActivity(intent);
+        }
+    }
+
     @Override
     public void onInterrupt() {
     }
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_background.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_background.xml
new file mode 100644
index 0000000..58fe368
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_background.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/magnifier_corner_radius" />
+            <stroke
+                android:color="@color/magnification_border_color"
+                android:width="@dimen/magnifier_border_width"/>
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/magnifier_outer_corner_radius" />
+            <stroke
+                android:color="@android:color/black"
+                android:width="@dimen/magnifier_stroke_width"/>
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_background_change.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_background_change.xml
new file mode 100644
index 0000000..693545b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_background_change.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/magnifier_edit_corner_radius" />
+            <stroke
+                android:color="@color/magnification_border_color"
+                android:width="@dimen/magnifier_border_width"
+                android:dashWidth="@dimen/magnifier_edit_dash_gap"
+                android:dashGap="@dimen/magnifier_edit_dash_gap"/>
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/magnifier_edit_outer_corner_radius" />
+            <stroke
+                android:color="@android:color/black"
+                android:width="@dimen/magnifier_stroke_width"/>
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_corner_background.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_corner_background.xml
new file mode 100644
index 0000000..c8cd4c9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_corner_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <size
+        android:width="@dimen/magnification_window_drag_corner_size"
+        android:height="@dimen/magnification_window_drag_corner_size"/>
+    <stroke
+        android:color="@android:color/black"
+        android:width="@dimen/magnification_window_drag_corner_stroke"/>
+    <solid android:color="@color/magnification_drag_corner_background" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background.xml
new file mode 100644
index 0000000..a52e805
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <stroke
+        android:color="@android:color/black"
+        android:width="@dimen/magnifier_stroke_width"/>
+    <corners android:radius="@dimen/magnifier_corner_radius" />
+    <solid android:color="@color/magnification_border_color" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_arrow_top_left_and_bottom_right.xml b/packages/SystemUI/res/drawable/ic_arrow_top_left_and_bottom_right.xml
new file mode 100644
index 0000000..9cac38b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_top_left_and_bottom_right.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="12dp"
+        android:height="12dp"
+        android:viewportWidth="12"
+        android:viewportHeight="12">
+    <path
+        android:pathData="M4.304,5.717L8.731,10.141L6.903,11.969L11.959,11.953L11.973,6.899L10.131,8.74L5.703,4.318L3.229,1.843L5.073,0L0,0L0,5.071L1.83,3.243L4.304,5.717Z"
+        android:fillColor="#000000"
+        android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_arrow_top_right_and_bottom_left.xml b/packages/SystemUI/res/drawable/ic_arrow_top_right_and_bottom_left.xml
new file mode 100644
index 0000000..0a87441
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_top_right_and_bottom_left.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="12dp"
+        android:height="12dp"
+        android:viewportWidth="12"
+        android:viewportHeight="12">
+    <group
+        android:pivotX="6"
+        android:pivotY="6"
+        android:rotation="90">
+        <path
+            android:pathData="M4.304,5.717L8.731,10.141L6.903,11.969L11.959,11.953L11.973,6.899L10.131,8.74L5.703,4.318L3.229,1.843L5.073,0L0,0L0,5.071L1.83,3.243L4.304,5.717Z"
+            android:fillColor="#000000"
+            android:fillType="evenOdd"/>
+    </group>
+</vector>
+
+
diff --git a/packages/SystemUI/res/drawable/ic_move_magnification.xml b/packages/SystemUI/res/drawable/ic_move_magnification.xml
index dfedd28..641bb43 100644
--- a/packages/SystemUI/res/drawable/ic_move_magnification.xml
+++ b/packages/SystemUI/res/drawable/ic_move_magnification.xml
@@ -20,6 +20,6 @@
     android:viewportHeight="24"
     android:tint="?attr/colorControlNormal">
   <path
-      android:fillColor="@android:color/white"
+      android:fillColor="@color/magnification_drag_handle_tint"
       android:pathData="M12,15Q10.75,15 9.875,14.125Q9,13.25 9,12Q9,10.75 9.875,9.875Q10.75,9 12,9Q13.25,9 14.125,9.875Q15,10.75 15,12Q15,13.25 14.125,14.125Q13.25,15 12,15ZM12,22 L7.75,17.75 9.15,16.35 12,19.15 14.85,16.35 16.25,17.75ZM6.25,16.25 L2,12 6.25,7.75 7.65,9.15 4.85,12 7.65,14.85ZM9.15,7.65 L7.75,6.25 12,2 16.25,6.25 14.85,7.65 12,4.85ZM17.75,16.25 L16.35,14.85 19.15,12 16.35,9.15 17.75,7.75 22,12Z"/>
 </vector>
diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
index c181724..392d845 100644
--- a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
+++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
@@ -4,12 +4,16 @@
     android:id="@+id/work_profile_first_run"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
+    android:paddingStart="16dp"
+    android:paddingEnd="4dp"
+    android:paddingVertical="16dp"
     android:visibility="gone">
     <ImageView
         android:id="@+id/screenshot_message_icon"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:paddingEnd="4dp"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_marginEnd="12dp"
+        android:layout_gravity="center_vertical"
         android:src="@drawable/ic_work_app_badge"/>
 
     <TextView
@@ -17,7 +21,11 @@
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_weight="1"
-        android:layout_gravity="start"/>
+        android:layout_gravity="start|center_vertical"
+        android:textSize="18sp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:lineHeight="24sp"
+        />
 
     <FrameLayout
         android:id="@+id/message_dismiss_button"
@@ -25,9 +33,9 @@
         android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
         android:contentDescription="@string/screenshot_dismiss_work_profile">
         <ImageView
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_margin="@dimen/overlay_dismiss_button_margin"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_gravity="center"
             android:src="@drawable/overlay_cancel"/>
     </FrameLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml b/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml
new file mode 100644
index 0000000..f20b582
--- /dev/null
+++ b/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:tools="http://schemas.android.com/tools"
+              android:id="@+id/seekbar_frame"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:clipChildren="false"
+              android:gravity="center_vertical"
+              android:orientation="horizontal"
+              tools:parentTag="android.widget.LinearLayout">
+
+    <ImageView
+        android:id="@+id/icon_start"
+        android:layout_width="@dimen/magnification_setting_seekbar_icon_size"
+        android:layout_height="@dimen/magnification_setting_seekbar_icon_size"
+        android:layout_gravity="center"
+        android:background="?android:attr/selectableItemBackgroundBorderless"
+        android:adjustViewBounds="true"
+        android:focusable="true"
+        android:src="@drawable/ic_remove"
+        android:tint="?android:attr/textColorPrimary"
+        android:tintMode="src_in" />
+
+    <SeekBar
+        android:id="@+id/seekbar"
+        style="@android:style/Widget.Material.SeekBar.Discrete"
+        android:layout_width="0dp"
+        android:layout_height="48dp"
+        android:layout_gravity="center_vertical"
+        android:layout_weight="1" />
+
+    <ImageView
+        android:id="@+id/icon_end"
+        android:layout_width="@dimen/magnification_setting_seekbar_icon_size"
+        android:layout_height="@dimen/magnification_setting_seekbar_icon_size"
+        android:layout_gravity="center"
+        android:background="?android:attr/selectableItemBackgroundBorderless"
+        android:adjustViewBounds="true"
+        android:focusable="true"
+        android:src="@drawable/ic_add"
+        android:tint="?android:attr/textColorPrimary"
+        android:tintMode="src_in" />
+
+</merge>
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index 377eb97..7dfe7c4 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -15,6 +15,7 @@
     limitations under the License.
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/magnifier_panel_view"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
@@ -129,13 +130,19 @@
             android:layout_weight="1"
             android:singleLine="true"
             android:text="@string/accessibility_allow_diagonal_scrolling"
-            android:textAppearance="@style/TextAppearance.MagnificationSetting.Title" />
+            android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
+            android:layout_gravity="center_vertical" />
 
         <Switch
             android:id="@+id/magnifier_horizontal_lock_switch"
-            android:layout_width="wrap_content"
+            android:switchMinWidth="@dimen/settingslib_switch_track_width"
+            android:layout_gravity="right|center_vertical"
+            android:layout_width="@dimen/settingslib_switch_track_width"
             android:layout_height="wrap_content"
-            android:layout_gravity="right" />
+            android:track="@drawable/settingslib_track_selector"
+            android:thumb="@drawable/settingslib_thumb_selector"
+            android:theme="@style/MainSwitch.Settingslib"/>
+
     </LinearLayout>
 
     <TextView
@@ -145,13 +152,14 @@
         android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
         android:focusable="true" />
 
-    <SeekBar
-        android:id="@+id/magnifier_zoom_seekbar"
-        android:layout_height="wrap_content"
+    <com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+        android:id="@+id/magnifier_zoom_slider"
         android:layout_width="match_parent"
-        android:layout_marginTop="@dimen/magnification_setting_seekbar_margin"
-        android:progress="0"
-        android:max="6" />
+        android:layout_height="wrap_content"
+        app:max="6"
+        app:progress="0"
+        app:iconStartContentDescription="@string/accessibility_control_zoom_out"
+        app:iconEndContentDescription="@string/accessibility_control_zoom_in"/>
 
     <Button
         android:id="@+id/magnifier_done_button"
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 0be7328..a8a048d 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -16,6 +16,7 @@
   -->
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
 
@@ -24,7 +25,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_margin="@dimen/magnification_outer_border_margin"
-        android:background="@color/magnification_border_color"/>
+        android:background="@drawable/accessibility_window_magnification_background"/>
 
     <RelativeLayout
         android:layout_width="match_parent"
@@ -66,54 +67,50 @@
 
     <ImageView
         android:id="@+id/top_right_corner"
+        android:background="@drawable/accessibility_window_magnification_drag_corner_background"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_margin="@dimen/magnification_drag_corner_margin"
         android:layout_gravity="right|top"
-        android:paddingTop="@dimen/magnifier_drag_handle_padding"
-        android:paddingEnd="@dimen/magnifier_drag_handle_padding"
         android:scaleType="center"
         android:contentDescription="@string/magnification_drag_corner_to_resize"
-        android:src="@drawable/ic_magnification_corner_top_right"
+        android:src="@drawable/ic_arrow_top_right_and_bottom_left"
         android:accessibilityTraversalAfter="@id/top_left_corner"/>
 
     <ImageView
         android:id="@+id/top_left_corner"
+        android:background="@drawable/accessibility_window_magnification_drag_corner_background"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_margin="@dimen/magnification_drag_corner_margin"
         android:layout_gravity="left|top"
-        android:paddingTop="@dimen/magnifier_drag_handle_padding"
-        android:paddingStart="@dimen/magnifier_drag_handle_padding"
         android:scaleType="center"
         android:contentDescription="@string/magnification_drag_corner_to_resize"
-        android:src="@drawable/ic_magnification_corner_top_left"
+        android:src="@drawable/ic_arrow_top_left_and_bottom_right"
         android:accessibilityTraversalAfter="@id/bottom_handle"/>
 
     <ImageView
         android:id="@+id/bottom_right_corner"
+        android:background="@drawable/accessibility_window_magnification_drag_corner_background"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_margin="@dimen/magnification_drag_corner_margin"
         android:layout_gravity="right|bottom"
-        android:paddingEnd="@dimen/magnifier_drag_handle_padding"
-        android:paddingBottom="@dimen/magnifier_drag_handle_padding"
         android:scaleType="center"
         android:contentDescription="@string/magnification_drag_corner_to_resize"
-        android:src="@drawable/ic_magnification_corner_bottom_right"
+        android:src="@drawable/ic_arrow_top_left_and_bottom_right"
         android:accessibilityTraversalAfter="@id/top_right_corner"/>
 
     <ImageView
         android:id="@+id/bottom_left_corner"
+        android:background="@drawable/accessibility_window_magnification_drag_corner_background"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_margin="@dimen/magnification_outer_border_margin"
+        android:layout_margin="@dimen/magnification_drag_corner_margin"
         android:layout_gravity="left|bottom"
-        android:paddingStart="@dimen/magnifier_drag_handle_padding"
-        android:paddingBottom="@dimen/magnifier_drag_handle_padding"
         android:scaleType="center"
         android:contentDescription="@string/magnification_drag_corner_to_resize"
-        android:src="@drawable/ic_magnification_corner_bottom_left"
+        android:src="@drawable/ic_arrow_top_right_and_bottom_left"
         android:accessibilityTraversalAfter="@id/bottom_right_corner"/>
 
     <ImageView
@@ -124,7 +121,8 @@
         android:layout_gravity="right|bottom"
         android:padding="@dimen/magnifier_drag_handle_padding"
         android:scaleType="center"
-        android:src="@drawable/ic_move_magnification"/>
+        android:src="@drawable/ic_move_magnification"
+        android:background="@drawable/accessibility_window_magnification_drag_handle_background"/>
 
     <ImageView
         android:id="@+id/close_button"
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 5b6c9d3..b3256ef 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -219,5 +219,12 @@
         <attr name="biometricsEnrollProgressHelp" format="reference|color" />
         <attr name="biometricsEnrollProgressHelpWithTalkback" format="reference|color" />
     </declare-styleable>
+
+    <declare-styleable name="SeekBarWithIconButtonsView_Layout">
+        <attr name="max" format="integer" />
+        <attr name="progress" format="integer" />
+        <attr name="iconStartContentDescription" format="reference" />
+        <attr name="iconEndContentDescription" format="reference" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5bb96c4..7360c79 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -168,10 +168,11 @@
     <color name="GM2_yellow_200">#FDE293</color>
 
     <!-- Window magnification colors -->
-    <color name="magnification_border_color">#FF9900</color>
-    <color name="magnification_border_color_change">#0000FF</color>
+    <color name="magnification_border_color">#F29900</color>
     <color name="magnification_switch_button_color">#7F000000</color>
+    <color name="magnification_drag_corner_background">#E5FFFFFF</color>
     <color name="magnification_drag_handle_color">#B3000000</color>
+    <color name="magnification_drag_handle_tint">#111111</color>
     <color name="accessibility_magnifier_bg">#FCFCFC</color>
     <color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
     <color name="accessibility_magnifier_icon_color">#252525</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 80756f1..ebf232f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1099,6 +1099,7 @@
     <dimen name="magnification_outer_border_margin">15dp</dimen>
     <dimen name="magnification_inner_border_margin">17dp</dimen>
     <dimen name="magnification_mirror_surface_margin">20dp</dimen>
+    <dimen name="magnification_drag_corner_margin">8dp</dimen>
     <dimen name="magnification_frame_move_short">5dp</dimen>
     <dimen name="magnification_frame_move_long">25dp</dimen>
     <dimen name="magnification_drag_view_size">36dp</dimen>
@@ -1111,6 +1112,15 @@
     <dimen name="magnifier_left_right_controls_height">45dp</dimen>
     <dimen name="magnifier_up_down_controls_width">45dp</dimen>
     <dimen name="magnifier_up_down_controls_height">40dp</dimen>
+    <dimen name="magnifier_outer_corner_radius">30dp</dimen>
+    <dimen name="magnifier_corner_radius">28dp</dimen>
+    <dimen name="magnifier_edit_corner_radius">16dp</dimen>
+    <dimen name="magnifier_edit_outer_corner_radius">18dp</dimen>
+    <dimen name="magnifier_border_width">8dp</dimen>
+    <dimen name="magnifier_stroke_width">2dp</dimen>
+    <dimen name="magnifier_edit_dash_gap">20dp</dimen>
+    <dimen name="magnification_window_drag_corner_size">26dp</dimen>
+    <dimen name="magnification_window_drag_corner_stroke">3dp</dimen>
     <!-- The extra padding to show the whole outer border -->
     <dimen name="magnifier_drag_handle_padding">3dp</dimen>
     <dimen name="magnification_max_frame_size">300dp</dimen>
@@ -1134,6 +1144,8 @@
     <dimen name="magnification_setting_image_button_padding_horizontal">24dp</dimen>
     <dimen name="magnification_setting_image_button_open_in_full_padding_vertical">16dp</dimen>
     <dimen name="magnification_setting_image_button_open_in_full_padding_horizontal">28dp</dimen>
+    <dimen name="magnification_setting_seekbar_icon_size">24dp</dimen>
+
 
     <!-- How far from the right edge of the screen you need to drag the window before the button
          repositions to the other side. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 16bffb7..0f94628 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -240,7 +240,7 @@
     <!-- Content description for the right boundary of the screenshot being cropped, with the current position as a percentage. [CHAR LIMIT=NONE] -->
     <string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
     <!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
-    <string name="screenshot_work_profile_notification">Work screenshots are saved in the <xliff:g id="app" example="Work Files">%1$s</xliff:g> app</string>
+    <string name="screenshot_work_profile_notification">Saved in <xliff:g id="app" example="Files">%1$s</xliff:g> in the work profile</string>
     <!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] -->
     <string name="screenshot_default_files_app_name">Files</string>
     <!-- A notice shown to the user to indicate that an app has detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 0685794..dd42c8e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -91,6 +91,7 @@
     private ViewGroup mStatusArea;
 
     // If the SMARTSPACE flag is set, keyguard_slice_view is replaced by the following views.
+    private ViewGroup mDateWeatherView;
     private View mWeatherView;
     private View mSmartspaceView;
 
@@ -201,7 +202,7 @@
             // TODO(b/261757708): add content observer for the Settings toggle and add/remove
             //  weather according to the Settings.
             if (mSmartspaceController.isDateWeatherDecoupled()) {
-                addWeatherView(viewIndex);
+                addDateWeatherView(viewIndex);
                 viewIndex += 1;
             }
 
@@ -239,6 +240,14 @@
 
     void onLocaleListChanged() {
         if (mSmartspaceController.isEnabled()) {
+            if (mSmartspaceController.isDateWeatherDecoupled()) {
+                mDateWeatherView.removeView(mWeatherView);
+                int index = mStatusArea.indexOfChild(mDateWeatherView);
+                if (index >= 0) {
+                    mStatusArea.removeView(mDateWeatherView);
+                    addDateWeatherView(index);
+                }
+            }
             int index = mStatusArea.indexOfChild(mSmartspaceView);
             if (index >= 0) {
                 mStatusArea.removeView(mSmartspaceView);
@@ -247,16 +256,28 @@
         }
     }
 
-    private void addWeatherView(int index) {
-        mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
+    private void addDateWeatherView(int index) {
+        mDateWeatherView = (ViewGroup) mSmartspaceController.buildAndConnectDateView(mView);
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                 MATCH_PARENT, WRAP_CONTENT);
-        mStatusArea.addView(mWeatherView, index, lp);
+        mStatusArea.addView(mDateWeatherView, index, lp);
         int startPadding = getContext().getResources().getDimensionPixelSize(
                 R.dimen.below_clock_padding_start);
         int endPadding = getContext().getResources().getDimensionPixelSize(
                 R.dimen.below_clock_padding_end);
-        mWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
+        mDateWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
+
+        addWeatherView();
+    }
+
+    private void addWeatherView() {
+        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                WRAP_CONTENT, WRAP_CONTENT);
+        mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
+        // Place weather right after the date, before the extras
+        final int index = mDateWeatherView.getChildCount() == 0 ? 0 : 1;
+        mDateWeatherView.addView(mWeatherView, index, lp);
+        mWeatherView.setPaddingRelative(0, 0, 4, 0);
     }
 
     private void addSmartspaceView(int index) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 08e9cf6..2a389b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.widget.FrameLayout;
 
 /**
@@ -33,7 +34,7 @@
 public class KeyguardHostView extends FrameLayout {
 
     protected ViewMediatorCallback mViewMediatorCallback;
-
+    private boolean mIsInteractable;
 
     public KeyguardHostView(Context context) {
         this(context, null);
@@ -54,4 +55,24 @@
     public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
         mViewMediatorCallback = viewMediatorCallback;
     }
+
+    /** Set true if the view can be interacted with */
+    public void setInteractable(boolean isInteractable) {
+        mIsInteractable = isInteractable;
+    }
+
+    /**
+     * Make sure to disallow touches while transitioning the bouncer, otherwise
+     * it can remain interactable even when barely visible.
+     */
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return !mIsInteractable;
+    }
+
+    /** True to consume any events that are sent to it */
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return true;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index ea84438..6139403 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -527,4 +527,9 @@
             mKeyguardSecurityContainerController.updateKeyguardPosition(x);
         }
     }
+
+    /** Set true if the view can be interacted with */
+    public void setInteractable(boolean isInteractable) {
+        mView.setInteractable(isInteractable);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index ffdd861..8578845 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -38,6 +38,7 @@
 import android.util.TimingsTraceLog;
 import android.view.SurfaceControl;
 import android.view.ThreadedRenderer;
+import android.view.View;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.systemui.dagger.GlobalRootComponent;
@@ -112,6 +113,11 @@
         // the theme set there.
         setTheme(R.style.Theme_SystemUI);
 
+        View.setTraceLayoutSteps(
+                SystemProperties.getBoolean("persist.debug.trace_layouts", false));
+        View.setTracedRequestLayoutClassClass(
+                SystemProperties.get("persist.debug.trace_request_layout_class", null));
+
         if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
             IntentFilter bootCompletedFilter = new
                     IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 0a0a36b..b111e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -49,6 +49,7 @@
 import android.util.Log;
 import android.util.Range;
 import android.util.Size;
+import android.util.TypedValue;
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.Gravity;
@@ -1273,9 +1274,19 @@
     }
 
     private void applyResourcesValues() {
-        mMirrorBorderView.setBackgroundColor(mResources.getColor(mEditSizeEnable
-                ? R.color.magnification_border_color_change : R.color.magnification_border_color));
+        // Sets the border appearance for the magnifier window
+        mMirrorBorderView.setBackground(mResources.getDrawable(mEditSizeEnable
+                ? R.drawable.accessibility_window_magnification_background_change
+                : R.drawable.accessibility_window_magnification_background));
 
+        // Changes the corner radius of the mMirrorSurfaceView
+        mMirrorSurfaceView.setCornerRadius(
+                        TypedValue.applyDimension(
+                                TypedValue.COMPLEX_UNIT_DIP,
+                                mEditSizeEnable ? 16f : 28f,
+                                mContext.getResources().getDisplayMetrics()));
+
+        // Sets visibility of components for the magnifier window
         if (mEditSizeEnable) {
             mDragView.setVisibility(View.GONE);
             mCloseView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 80110c7..4c1a9fa 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -53,6 +53,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.R;
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.lang.annotation.Retention;
@@ -79,7 +80,7 @@
     private final MagnificationGestureDetector mGestureDetector;
     private boolean mSingleTapDetected = false;
 
-    private SeekBar mZoomSeekbar;
+    private SeekBarWithIconButtonsView mZoomSeekbar;
     private Switch mAllowDiagonalScrollingSwitch;
     private LinearLayout mPanelView;
     private LinearLayout mSettingView;
@@ -393,7 +394,8 @@
         mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
         mChangeModeButton = mSettingView.findViewById(R.id.magnifier_full_button);
 
-        mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_seekbar);
+        mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
+
         mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
 
         float scale = mSecureSettings.getFloatForUser(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt b/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt
new file mode 100644
index 0000000..ab9b690
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt
@@ -0,0 +1,13 @@
+package com.android.systemui.biometrics
+
+/**
+ * Collection of parameters used by EllipseOverlapDetector
+ *
+ * [minOverlap] minimum percentage (float from 0-1) needed to be considered a valid overlap
+ *
+ * [targetSize] percentage (defined as a float of 0-1) of sensor that is considered the target,
+ * expands outward from center
+ *
+ * [stepSize] size of each step when iterating over sensor pixel grid
+ */
+class EllipseOverlapDetectorParams(val minOverlap: Float, val targetSize: Float, val stepSize: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 7217f99..64d5518 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -557,7 +557,8 @@
         final InstanceId sessionIdProvider = mSessionTracker.getSessionId(
                 getBiometricSessionType());
         final int sessionId = (sessionIdProvider != null) ? sessionIdProvider.getId() : -1;
-        final int touchConfigId = BOUNDING_BOX_TOUCH_CONFIG_ID;
+        final int touchConfigId = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_selected_udfps_touch_detection);
 
         SysUiStatsLog.write(SysUiStatsLog.BIOMETRIC_TOUCH_REPORTED, biometricTouchReportedTouchType,
                 touchConfigId, sessionId, data.getX(), data.getY(), data.getMinor(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 6680787..45ca24d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -442,7 +442,8 @@
     ): WindowManager.LayoutParams {
         val paddingX = animation?.paddingX ?: 0
         val paddingY = animation?.paddingY ?: 0
-        if (animation != null && animation.listenForTouchesOutsideView()) {
+        if (!featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) && animation != null &&
+                animation.listenForTouchesOutsideView()) {
             flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
index 001fed7..20c3e40 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.biometrics.dagger
 
+import android.content.res.Resources
+import com.android.internal.R
+import com.android.systemui.biometrics.EllipseOverlapDetectorParams
 import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector
 import com.android.systemui.biometrics.udfps.EllipseOverlapDetector
 import com.android.systemui.biometrics.udfps.OverlapDetector
@@ -33,10 +36,30 @@
         @Provides
         @SysUISingleton
         fun providesOverlapDetector(featureFlags: FeatureFlags): OverlapDetector {
-            return if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
-                EllipseOverlapDetector()
+            if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+                val selectedOption =
+                    Resources.getSystem()
+                        .getInteger(R.integer.config_selected_udfps_touch_detection)
+                val values =
+                    Resources.getSystem()
+                        .getStringArray(R.array.config_udfps_touch_detection_options)[
+                            selectedOption]
+                        .split(",")
+                        .map { it.toFloat() }
+
+                return if (values[0] == 1f) {
+                    EllipseOverlapDetector(
+                        EllipseOverlapDetectorParams(
+                            minOverlap = values[3],
+                            targetSize = values[2],
+                            stepSize = values[4].toInt()
+                        )
+                    )
+                } else {
+                    BoundingBoxOverlapDetector()
+                }
             } else {
-                BoundingBoxOverlapDetector()
+                return BoundingBoxOverlapDetector()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
index 682d38a..2e2970f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
@@ -18,22 +18,84 @@
 
 import android.graphics.Point
 import android.graphics.Rect
-import androidx.annotation.VisibleForTesting
+import android.util.Log
+import com.android.systemui.biometrics.EllipseOverlapDetectorParams
 import com.android.systemui.dagger.SysUISingleton
 import kotlin.math.cos
-import kotlin.math.pow
 import kotlin.math.sin
 
+private enum class SensorPixelPosition {
+    OUTSIDE, // Pixel that falls outside of sensor circle
+    SENSOR, // Pixel within sensor circle
+    TARGET // Pixel within sensor center target
+}
+
+private val isDebug = true
+private val TAG = "EllipseOverlapDetector"
+
 /**
  * Approximates the touch as an ellipse and determines whether the ellipse has a sufficient overlap
  * with the sensor.
  */
 @SysUISingleton
-class EllipseOverlapDetector(private val neededPoints: Int = 2) : OverlapDetector {
-
+class EllipseOverlapDetector(private val params: EllipseOverlapDetectorParams) : OverlapDetector {
     override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
-        val points = calculateSensorPoints(nativeSensorBounds)
-        return points.count { checkPoint(it, touchData) } >= neededPoints
+        var isTargetTouched = false
+        var sensorPixels = 0
+        var coveredPixels = 0
+        for (y in nativeSensorBounds.top..nativeSensorBounds.bottom step params.stepSize) {
+            for (x in nativeSensorBounds.left..nativeSensorBounds.right step params.stepSize) {
+                // Check where pixel is within the sensor TODO: (b/265836919) This could be improved
+                // by precomputing these points
+                val pixelPosition =
+                    isPartOfSensorArea(
+                        x,
+                        y,
+                        nativeSensorBounds.centerX(),
+                        nativeSensorBounds.centerY(),
+                        nativeSensorBounds.width() / 2
+                    )
+                if (pixelPosition != SensorPixelPosition.OUTSIDE) {
+                    sensorPixels++
+
+                    // Check if this pixel falls within ellipse touch
+                    if (checkPoint(Point(x, y), touchData)) {
+                        coveredPixels++
+
+                        // Check that at least one covered pixel is within sensor target
+                        isTargetTouched =
+                            isTargetTouched or (pixelPosition == SensorPixelPosition.TARGET)
+                    }
+                }
+            }
+        }
+
+        val percentage: Float = coveredPixels.toFloat() / sensorPixels
+        if (isDebug) {
+            Log.v(
+                TAG,
+                "covered: $coveredPixels, sensor: $sensorPixels, " +
+                    "percentage: $percentage, isCenterTouched: $isTargetTouched"
+            )
+        }
+
+        return percentage >= params.minOverlap && isTargetTouched
+    }
+
+    /** Checks if point is in the sensor center target circle, outer circle, or outside of sensor */
+    private fun isPartOfSensorArea(x: Int, y: Int, cX: Int, cY: Int, r: Int): SensorPixelPosition {
+        val dx = cX - x
+        val dy = cY - y
+
+        val disSquared = dx * dx + dy * dy
+
+        return if (disSquared <= (r * params.targetSize) * (r * params.targetSize)) {
+            SensorPixelPosition.TARGET
+        } else if (disSquared <= r * r) {
+            SensorPixelPosition.SENSOR
+        } else {
+            SensorPixelPosition.OUTSIDE
+        }
     }
 
     private fun checkPoint(point: Point, touchData: NormalizedTouchData): Boolean {
@@ -45,29 +107,9 @@
         val c: Float = sin(touchData.orientation) * (point.x - touchData.x)
         val d: Float = cos(touchData.orientation) * (point.y - touchData.y)
         val result =
-            (a + b).pow(2) / (touchData.minor / 2).pow(2) +
-                (c - d).pow(2) / (touchData.major / 2).pow(2)
+            (a + b) * (a + b) / ((touchData.minor / 2) * (touchData.minor / 2)) +
+                (c - d) * (c - d) / ((touchData.major / 2) * (touchData.major / 2))
 
         return result <= 1
     }
-
-    @VisibleForTesting
-    fun calculateSensorPoints(sensorBounds: Rect): List<Point> {
-        val sensorX = sensorBounds.centerX()
-        val sensorY = sensorBounds.centerY()
-        val cornerOffset: Int = sensorBounds.width() / 4
-        val sideOffset: Int = sensorBounds.width() / 3
-
-        return listOf(
-            Point(sensorX - cornerOffset, sensorY - cornerOffset),
-            Point(sensorX, sensorY - sideOffset),
-            Point(sensorX + cornerOffset, sensorY - cornerOffset),
-            Point(sensorX - sideOffset, sensorY),
-            Point(sensorX, sensorY),
-            Point(sensorX + sideOffset, sensorY),
-            Point(sensorX - cornerOffset, sensorY + cornerOffset),
-            Point(sensorX, sensorY + sideOffset),
-            Point(sensorX + cornerOffset, sensorY + cornerOffset)
-        )
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
new file mode 100644
index 0000000..ae6a08f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -0,0 +1,185 @@
+/*
+ * 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.common.ui.view;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+
+import com.android.systemui.R;
+
+/**
+ * The layout contains a seekbar whose progress could be modified
+ * through the icons on two ends of the seekbar.
+ */
+public class SeekBarWithIconButtonsView extends LinearLayout {
+
+    private static final int DEFAULT_SEEKBAR_MAX = 6;
+    private static final int DEFAULT_SEEKBAR_PROGRESS = 0;
+
+    private ImageView mIconStart;
+    private ImageView mIconEnd;
+    private SeekBar mSeekbar;
+
+    private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
+
+    public SeekBarWithIconButtonsView(Context context) {
+        this(context, null);
+    }
+
+    public SeekBarWithIconButtonsView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SeekBarWithIconButtonsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public SeekBarWithIconButtonsView(Context context,
+            AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        LayoutInflater.from(context).inflate(
+                R.layout.seekbar_with_icon_buttons, this, /* attachToRoot= */ true);
+
+        mIconStart = findViewById(R.id.icon_start);
+        mIconEnd = findViewById(R.id.icon_end);
+        mSeekbar = findViewById(R.id.seekbar);
+
+        if (attrs != null) {
+            TypedArray typedArray = context.obtainStyledAttributes(
+                    attrs,
+                    R.styleable.SeekBarWithIconButtonsView_Layout,
+                    defStyleAttr, defStyleRes
+            );
+            int max = typedArray.getInt(
+                    R.styleable.SeekBarWithIconButtonsView_Layout_max, DEFAULT_SEEKBAR_MAX);
+            int progress = typedArray.getInt(
+                    R.styleable.SeekBarWithIconButtonsView_Layout_progress,
+                    DEFAULT_SEEKBAR_PROGRESS);
+            mSeekbar.setMax(max);
+            setProgress(progress);
+
+            int iconStartContentDescriptionId = typedArray.getResourceId(
+                    R.styleable.SeekBarWithIconButtonsView_Layout_iconStartContentDescription,
+                    /* defValue= */ 0);
+            int iconEndContentDescriptionId = typedArray.getResourceId(
+                    R.styleable.SeekBarWithIconButtonsView_Layout_iconEndContentDescription,
+                    /* defValue= */ 0);
+            if (iconStartContentDescriptionId != 0) {
+                final String contentDescription =
+                        context.getString(iconStartContentDescriptionId);
+                mIconStart.setContentDescription(contentDescription);
+            }
+            if (iconEndContentDescriptionId != 0) {
+                final String contentDescription =
+                        context.getString(iconEndContentDescriptionId);
+                mIconEnd.setContentDescription(contentDescription);
+            }
+
+            typedArray.recycle();
+        } else {
+            mSeekbar.setMax(DEFAULT_SEEKBAR_MAX);
+            setProgress(DEFAULT_SEEKBAR_PROGRESS);
+        }
+
+        mSeekbar.setOnSeekBarChangeListener(mSeekBarListener);
+
+        mIconStart.setOnClickListener((view) -> {
+            final int progress = mSeekbar.getProgress();
+            if (progress > 0) {
+                mSeekbar.setProgress(progress - 1);
+                setIconViewEnabled(mIconStart, mSeekbar.getProgress() > 0);
+            }
+        });
+
+        mIconEnd.setOnClickListener((view) -> {
+            final int progress = mSeekbar.getProgress();
+            if (progress < mSeekbar.getMax()) {
+                mSeekbar.setProgress(progress + 1);
+                setIconViewEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
+            }
+        });
+    }
+
+    private static void setIconViewEnabled(View iconView, boolean enabled) {
+        iconView.setEnabled(enabled);
+    }
+
+    /**
+     * Sets a onSeekbarChangeListener to the seekbar in the layout.
+     * We update the Start Icon and End Icon if needed when the seekbar progress is changed.
+     */
+    public void setOnSeekBarChangeListener(
+            @Nullable SeekBar.OnSeekBarChangeListener onSeekBarChangeListener) {
+        mSeekBarListener.setOnSeekBarChangeListener(onSeekBarChangeListener);
+    }
+
+    /**
+     * Start and End icons might need to be updated when there is a change in seekbar progress.
+     * Icon Start will need to be enabled when the seekbar progress is larger than 0.
+     * Icon End will need to be enabled when the seekbar progress is less than Max.
+     */
+    private void updateIconViewIfNeeded(int progress) {
+        setIconViewEnabled(mIconStart, progress > 0);
+        setIconViewEnabled(mIconEnd, progress < mSeekbar.getMax());
+    }
+
+    /**
+     * Sets progress to the seekbar in the layout.
+     */
+    public void setProgress(int progress) {
+        mSeekbar.setProgress(progress);
+        updateIconViewIfNeeded(progress);
+    }
+
+    private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
+        private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = null;
+
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            if (mOnSeekBarChangeListener != null) {
+                mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+            }
+            updateIconViewIfNeeded(progress);
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+            if (mOnSeekBarChangeListener != null) {
+                mOnSeekBarChangeListener.onStartTrackingTouch(seekBar);
+            }
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            if (mOnSeekBarChangeListener != null) {
+                mOnSeekBarChangeListener.onStopTrackingTouch(seekBar);
+            }
+        }
+
+        void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) {
+            mOnSeekBarChangeListener = listener;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index c3f24f0..45872f4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -218,6 +218,10 @@
     abstract BcSmartspaceConfigPlugin optionalBcSmartspaceConfigPlugin();
 
     @BindsOptionalOf
+    @Named(SmartspaceModule.DATE_SMARTSPACE_DATA_PLUGIN)
+    abstract BcSmartspaceDataPlugin optionalDateSmartspaceConfigPlugin();
+
+    @BindsOptionalOf
     @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
     abstract BcSmartspaceDataPlugin optionalWeatherSmartspaceConfigPlugin();
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 215be80..cd911c1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -347,7 +347,7 @@
         unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true)
 
     // TODO(b/263512203): Tracking Bug
-    val MEDIA_EXPLICIT_INDICATOR = unreleasedFlag(911, "media_explicit_indicator", teamfood = true)
+    val MEDIA_EXPLICIT_INDICATOR = releasedFlag(911, "media_explicit_indicator")
 
     // TODO(b/265813373): Tracking Bug
     val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE =
@@ -360,6 +360,9 @@
     @JvmField
     val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
 
+    // TODO(b/267007629): Tracking Bug
+    val MEDIA_RESUME_PROGRESS = unreleasedFlag(915, "media_resume_progress")
+
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
 
@@ -522,10 +525,19 @@
     val QUICK_TAP_FLOW_FRAMEWORK =
         unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false)
 
-    // 1500 - chooser
+    // 1500 - chooser aka sharesheet
     // TODO(b/254512507): Tracking Bug
     val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true)
 
+    // TODO(b/266983432) Tracking Bug
+    val SHARESHEET_CUSTOM_ACTIONS = unreleasedFlag(1501, "sharesheet_custom_actions")
+
+    // TODO(b/266982749) Tracking Bug
+    val SHARESHEET_RESELECTION_ACTION = unreleasedFlag(1502, "sharesheet_reselection_action")
+
+    // TODO(b/266983474) Tracking Bug
+    val SHARESHEET_IMAGE_AND_TEXT_PREVIEW = unreleasedFlag(1503, "sharesheet_image_text_preview")
+
     // 1600 - accessibility
     // TODO(b/262224538): Tracking Bug
     @JvmField
@@ -560,9 +572,8 @@
 
     // 2200 - udfps
     // TODO(b/259264861): Tracking Bug
-    @JvmField val UDFPS_NEW_TOUCH_DETECTION = unreleasedFlag(2200, "udfps_new_touch_detection")
-    @JvmField val UDFPS_ELLIPSE_DEBUG_UI = unreleasedFlag(2201, "udfps_ellipse_debug")
-    @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
+    @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
+    @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection")
 
     // 2300 - stylus
     @JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 35ae9b4..7bc6c34 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -41,31 +41,96 @@
  *
  * Make sure to add newly added flows to the logger.
  */
+interface KeyguardBouncerRepository {
+    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
+    val primaryBouncerVisible: StateFlow<Boolean>
+    val primaryBouncerShow: StateFlow<KeyguardBouncerModel?>
+    val primaryBouncerShowingSoon: StateFlow<Boolean>
+    val primaryBouncerHide: StateFlow<Boolean>
+    val primaryBouncerStartingToHide: StateFlow<Boolean>
+    val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
+    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
+    val primaryBouncerScrimmed: StateFlow<Boolean>
+    /**
+     * Set how much of the notification panel is showing on the screen.
+     * ```
+     *      0f = panel fully hidden = bouncer fully showing
+     *      1f = panel fully showing = bouncer fully hidden
+     * ```
+     */
+    val panelExpansionAmount: StateFlow<Float>
+    val keyguardPosition: StateFlow<Float>
+    val onScreenTurnedOff: StateFlow<Boolean>
+    val isBackButtonEnabled: StateFlow<Boolean?>
+    /** Determines if user is already unlocked */
+    val keyguardAuthenticated: StateFlow<Boolean?>
+    val showMessage: StateFlow<BouncerShowMessageModel?>
+    val resourceUpdateRequests: StateFlow<Boolean>
+    val bouncerPromptReason: Int
+    val bouncerErrorMessage: CharSequence?
+    val isAlternateBouncerVisible: StateFlow<Boolean>
+    val isAlternateBouncerUIAvailable: StateFlow<Boolean>
+    var lastAlternateBouncerVisibleTime: Long
+
+    fun setPrimaryScrimmed(isScrimmed: Boolean)
+
+    fun setPrimaryVisible(isVisible: Boolean)
+
+    fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?)
+
+    fun setPrimaryShowingSoon(showingSoon: Boolean)
+
+    fun setPrimaryHide(hide: Boolean)
+
+    fun setPrimaryStartingToHide(startingToHide: Boolean)
+
+    fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
+
+    fun setPanelExpansion(panelExpansion: Float)
+
+    fun setKeyguardPosition(keyguardPosition: Float)
+
+    fun setResourceUpdateRequests(willUpdateResources: Boolean)
+
+    fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
+
+    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
+
+    fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
+
+    fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean)
+
+    fun setAlternateVisible(isVisible: Boolean)
+
+    fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
+}
+
 @SysUISingleton
-class KeyguardBouncerRepository
+class KeyguardBouncerRepositoryImpl
 @Inject
 constructor(
     private val viewMediatorCallback: ViewMediatorCallback,
     private val clock: SystemClock,
     @Application private val applicationScope: CoroutineScope,
     @BouncerLog private val buffer: TableLogBuffer,
-) {
+) : KeyguardBouncerRepository {
     /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
     private val _primaryBouncerVisible = MutableStateFlow(false)
-    val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
+    override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
     private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
-    val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
     private val _primaryBouncerShowingSoon = MutableStateFlow(false)
-    val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
     private val _primaryBouncerHide = MutableStateFlow(false)
-    val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
+    override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
     private val _primaryBouncerStartingToHide = MutableStateFlow(false)
-    val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
     private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
-    val primaryBouncerStartingDisappearAnimation = _primaryBouncerDisappearAnimation.asStateFlow()
+    override val primaryBouncerStartingDisappearAnimation =
+        _primaryBouncerDisappearAnimation.asStateFlow()
     /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
     private val _primaryBouncerScrimmed = MutableStateFlow(false)
-    val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
     /**
      * Set how much of the notification panel is showing on the screen.
      * ```
@@ -74,46 +139,46 @@
      * ```
      */
     private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
-    val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
     private val _keyguardPosition = MutableStateFlow(0f)
-    val keyguardPosition = _keyguardPosition.asStateFlow()
+    override val keyguardPosition = _keyguardPosition.asStateFlow()
     private val _onScreenTurnedOff = MutableStateFlow(false)
-    val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+    override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
     private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
-    val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
     private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
     /** Determines if user is already unlocked */
-    val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
     private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
-    val showMessage = _showMessage.asStateFlow()
+    override val showMessage = _showMessage.asStateFlow()
     private val _resourceUpdateRequests = MutableStateFlow(false)
-    val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
-    val bouncerPromptReason: Int
+    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+    override val bouncerPromptReason: Int
         get() = viewMediatorCallback.bouncerPromptReason
-    val bouncerErrorMessage: CharSequence?
+    override val bouncerErrorMessage: CharSequence?
         get() = viewMediatorCallback.consumeCustomMessage()
 
     /** Values associated with the AlternateBouncer */
     private val _isAlternateBouncerVisible = MutableStateFlow(false)
-    val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
-    var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
+    override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
+    override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
     private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
-    val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
+    override val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
         _isAlternateBouncerUIAvailable.asStateFlow()
 
     init {
         setUpLogging()
     }
 
-    fun setPrimaryScrimmed(isScrimmed: Boolean) {
+    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
         _primaryBouncerScrimmed.value = isScrimmed
     }
 
-    fun setPrimaryVisible(isVisible: Boolean) {
+    override fun setPrimaryVisible(isVisible: Boolean) {
         _primaryBouncerVisible.value = isVisible
     }
 
-    fun setAlternateVisible(isVisible: Boolean) {
+    override fun setAlternateVisible(isVisible: Boolean) {
         if (isVisible && !_isAlternateBouncerVisible.value) {
             lastAlternateBouncerVisibleTime = clock.uptimeMillis()
         } else if (!isVisible) {
@@ -122,55 +187,55 @@
         _isAlternateBouncerVisible.value = isVisible
     }
 
-    fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
         _isAlternateBouncerUIAvailable.value = isAvailable
     }
 
-    fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+    override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
         _primaryBouncerShow.value = keyguardBouncerModel
     }
 
-    fun setPrimaryShowingSoon(showingSoon: Boolean) {
+    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
         _primaryBouncerShowingSoon.value = showingSoon
     }
 
-    fun setPrimaryHide(hide: Boolean) {
+    override fun setPrimaryHide(hide: Boolean) {
         _primaryBouncerHide.value = hide
     }
 
-    fun setPrimaryStartingToHide(startingToHide: Boolean) {
+    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
         _primaryBouncerStartingToHide.value = startingToHide
     }
 
-    fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
         _primaryBouncerDisappearAnimation.value = runnable
     }
 
-    fun setPanelExpansion(panelExpansion: Float) {
+    override fun setPanelExpansion(panelExpansion: Float) {
         _panelExpansionAmount.value = panelExpansion
     }
 
-    fun setKeyguardPosition(keyguardPosition: Float) {
+    override fun setKeyguardPosition(keyguardPosition: Float) {
         _keyguardPosition.value = keyguardPosition
     }
 
-    fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
         _resourceUpdateRequests.value = willUpdateResources
     }
 
-    fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
         _showMessage.value = bouncerShowMessageModel
     }
 
-    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
         _keyguardAuthenticated.value = keyguardAuthenticated
     }
 
-    fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
         _isBackButtonEnabled.value = isBackButtonEnabled
     }
 
-    fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
+    override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
         _onScreenTurnedOff.value = onScreenTurnedOff
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index cc99eb7..2cbc2bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -37,4 +37,7 @@
     fun deviceEntryFingerprintAuthRepository(
         impl: DeviceEntryFingerprintAuthRepositoryImpl
     ): DeviceEntryFingerprintAuthRepository
+
+    @Binds
+    fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index ce61f2f..86f65dde 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
@@ -44,6 +45,7 @@
 
     override fun start() {
         listenForDozingToLockscreen()
+        listenForDozingToGone()
     }
 
     private fun listenForDozingToLockscreen() {
@@ -68,6 +70,28 @@
         }
     }
 
+    private fun listenForDozingToGone() {
+        scope.launch {
+            keyguardInteractor.biometricUnlockState
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { (biometricUnlockState, lastStartedTransition) ->
+                    if (
+                        lastStartedTransition.to == KeyguardState.DOZING &&
+                            isWakeAndUnlock(biometricUnlockState)
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.DOZING,
+                                KeyguardState.GONE,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
     private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
         return ValueAnimator().apply {
             setInterpolator(Interpolators.LINEAR)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index ad6dbea..53c80f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -19,7 +19,6 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
@@ -31,9 +30,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import javax.inject.Inject
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.time.Duration
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
@@ -104,38 +100,4 @@
     /* The last completed [KeyguardState] transition */
     val finishedKeyguardState: Flow<KeyguardState> =
         finishedKeyguardTransitionStep.map { step -> step.to }
-
-    /**
-     * Transitions will occur over a [totalDuration] with [TransitionStep]s being emitted in the
-     * range of [0, 1]. View animations should begin and end within a subset of this range. This
-     * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
-     */
-    fun transitionStepAnimation(
-        flow: Flow<TransitionStep>,
-        params: AnimationParams,
-        totalDuration: Duration,
-    ): Flow<Float> {
-        val start = (params.startTime / totalDuration).toFloat()
-        val chunks = (totalDuration / params.duration).toFloat()
-        var isRunning = false
-        return flow
-            .map { step ->
-                val value = (step.value - start) * chunks
-                if (step.transitionState == STARTED) {
-                    // When starting, make sure to always emit. If a transition is started from the
-                    // middle, it is possible this animation is being skipped but we need to inform
-                    // the ViewModels of the last update
-                    isRunning = true
-                    max(0f, min(1f, value))
-                } else if (isRunning && value >= 1f) {
-                    // Always send a final value of 1. Because of rounding, [value] may never be
-                    // exactly 1.
-                    isRunning = false
-                    1f
-                } else {
-                    value
-                }
-            }
-            .filter { value -> value >= 0f && value <= 1f }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 6679b22..4579e37 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -111,6 +111,8 @@
                 0f
             }
         }
+    /** Allow for interaction when just about fully visible */
+    val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
 
     // TODO(b/243685699): Move isScrimmed logic to data layer.
     // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
deleted file mode 100644
index 67733e9..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.systemui.keyguard.shared.model
-
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.milliseconds
-
-/** Animation parameters */
-data class AnimationParams(
-    val startTime: Duration = 0.milliseconds,
-    val duration: Duration,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
new file mode 100644
index 0000000..ca1e27c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.ui
+
+import android.view.animation.Interpolator
+import com.android.systemui.animation.Interpolators.LINEAR
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/**
+ * For the given transition params, construct a flow using [createFlow] for the specified portion of
+ * the overall transition.
+ */
+class KeyguardTransitionAnimationFlow(
+    private val transitionDuration: Duration,
+    private val transitionFlow: Flow<TransitionStep>,
+) {
+    /**
+     * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted in
+     * the range of [0, 1]. View animations should begin and end within a subset of this range. This
+     * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
+     */
+    fun createFlow(
+        duration: Duration,
+        onStep: (Float) -> Float,
+        startTime: Duration = 0.milliseconds,
+        onCancel: (() -> Float)? = null,
+        onFinish: (() -> Float)? = null,
+        interpolator: Interpolator = LINEAR,
+    ): Flow<Float> {
+        if (!duration.isPositive()) {
+            throw IllegalArgumentException("duration must be a positive number: $duration")
+        }
+        if ((startTime + duration).compareTo(transitionDuration) > 0) {
+            throw IllegalArgumentException(
+                "startTime($startTime) + duration($duration) must be" +
+                    " <= transitionDuration($transitionDuration)"
+            )
+        }
+
+        val start = (startTime / transitionDuration).toFloat()
+        val chunks = (transitionDuration / duration).toFloat()
+        var isComplete = true
+
+        fun stepToValue(step: TransitionStep): Float? {
+            val value = (step.value - start) * chunks
+            return when (step.transitionState) {
+                // When starting, make sure to always emit. If a transition is started from the
+                // middle, it is possible this animation is being skipped but we need to inform
+                // the ViewModels of the last update
+                STARTED -> {
+                    isComplete = false
+                    max(0f, min(1f, value))
+                }
+                // Always send a final value of 1. Because of rounding, [value] may never be
+                // exactly 1.
+                RUNNING ->
+                    if (isComplete) {
+                        null
+                    } else if (value >= 1f) {
+                        isComplete = true
+                        1f
+                    } else if (value >= 0f) {
+                        value
+                    } else {
+                        null
+                    }
+                else -> null
+            }?.let { onStep(interpolator.getInterpolation(it)) }
+        }
+
+        return transitionFlow
+            .map { step ->
+                when (step.transitionState) {
+                    STARTED -> stepToValue(step)
+                    RUNNING -> stepToValue(step)
+                    CANCELED -> onCancel?.invoke()
+                    FINISHED -> onFinish?.invoke()
+                }
+            }
+            .filterNotNull()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index c1731e0..9f09d53 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -154,6 +154,12 @@
                     }
 
                     launch {
+                        viewModel.isInteractable.collect { isInteractable ->
+                            hostViewController.setInteractable(isInteractable)
+                        }
+                    }
+
+                    launch {
                         viewModel.isBouncerVisible
                             .filter { !it }
                             .collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 6627865..8d6545a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -21,15 +21,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
 
 /**
  * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -41,39 +36,46 @@
 constructor(
     private val interactor: KeyguardTransitionInteractor,
 ) {
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_LOCKSCREEN_DURATION,
+            transitionFlow = interactor.dreamingToLockscreenTransition,
+        )
 
     /** Dream overlay y-translation on exit */
     fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
-        return flowForAnimation(DREAM_OVERLAY_TRANSLATION_Y).map { value ->
-            EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx
-        }
+        return transitionAnimation.createFlow(
+            duration = 600.milliseconds,
+            onStep = { it * translatePx },
+            interpolator = EMPHASIZED_ACCELERATE,
+        )
     }
     /** Dream overlay views alpha - fade out */
-    val dreamOverlayAlpha: Flow<Float> = flowForAnimation(DREAM_OVERLAY_ALPHA).map { 1f - it }
+    val dreamOverlayAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 250.milliseconds,
+            onStep = { 1f - it },
+        )
 
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
-        return merge(
-            flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-                -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
-            },
-            // On end, reset the translation to 0
-            interactor.dreamingToLockscreenTransition
-                .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
-                .map { 0f }
+        return transitionAnimation.createFlow(
+            duration = TO_LOCKSCREEN_DURATION,
+            onStep = { value -> -translatePx + value * translatePx },
+            // Reset on cancel or finish
+            onFinish = { 0f },
+            onCancel = { 0f },
+            interpolator = EMPHASIZED_DECELERATE,
         )
     }
 
     /** Lockscreen views alpha */
-    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
-
-    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
-        return interactor.transitionStepAnimation(
-            interactor.dreamingToLockscreenTransition,
-            params,
-            totalDuration = TO_LOCKSCREEN_DURATION
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            startTime = 233.milliseconds,
+            duration = 250.milliseconds,
+            onStep = { it },
         )
-    }
 
     companion object {
         /* Length of time before ending the dream activity, in order to start unoccluding */
@@ -81,11 +83,5 @@
         @JvmField
         val LOCKSCREEN_ANIMATION_DURATION_MS =
             (TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds
-
-        val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 600.milliseconds)
-        val DREAM_OVERLAY_ALPHA = AnimationParams(duration = 250.milliseconds)
-        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
-        val LOCKSCREEN_ALPHA =
-            AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index 5a47960..f16827d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -20,15 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
 
 /** Breaks down GONE->DREAMING transition into discrete steps for corresponding views to consume. */
 @SysUISingleton
@@ -38,32 +33,28 @@
     private val interactor: KeyguardTransitionInteractor,
 ) {
 
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_DREAMING_DURATION,
+            transitionFlow = interactor.goneToDreamingTransition,
+        )
+
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
-        return merge(
-            flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-                (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
-            },
-            // On end, reset the translation to 0
-            interactor.goneToDreamingTransition
-                .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
-                .map { 0f }
+        return transitionAnimation.createFlow(
+            duration = 500.milliseconds,
+            onStep = { it * translatePx },
+            // Reset on cancel or finish
+            onFinish = { 0f },
+            onCancel = { 0f },
+            interpolator = EMPHASIZED_ACCELERATE,
         )
     }
 
     /** Lockscreen views alpha */
-    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
-    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
-        return interactor.transitionStepAnimation(
-            interactor.goneToDreamingTransition,
-            params,
-            totalDuration = TO_DREAMING_DURATION
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 250.milliseconds,
+            onStep = { 1f - it },
         )
-    }
-
-    companion object {
-        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
-        val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 737c35d..b8b3a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -39,6 +39,9 @@
     /** Observe on bouncer visibility. */
     val isBouncerVisible: Flow<Boolean> = interactor.isVisible
 
+    /** Can the user interact with the view? */
+    val isInteractable: Flow<Boolean> = interactor.isInteractable
+
     /** Observe whether bouncer is showing. */
     val show: Flow<KeyguardBouncerModel> = interactor.show
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index e05adbd..bc9dc4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -20,15 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
 
 /**
  * Breaks down LOCKSCREEN->DREAMING transition into discrete steps for corresponding views to
@@ -40,35 +35,32 @@
 constructor(
     private val interactor: KeyguardTransitionInteractor,
 ) {
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_DREAMING_DURATION,
+            transitionFlow = interactor.lockscreenToDreamingTransition,
+        )
 
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
-        return merge(
-            flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-                (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
-            },
-            // On end, reset the translation to 0
-            interactor.lockscreenToDreamingTransition
-                .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
-                .map { 0f }
+        return transitionAnimation.createFlow(
+            duration = 500.milliseconds,
+            onStep = { it * translatePx },
+            // Reset on cancel or finish
+            onFinish = { 0f },
+            onCancel = { 0f },
+            interpolator = EMPHASIZED_ACCELERATE,
         )
     }
 
     /** Lockscreen views alpha */
-    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
-    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
-        return interactor.transitionStepAnimation(
-            interactor.lockscreenToDreamingTransition,
-            params,
-            totalDuration = TO_DREAMING_DURATION
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 250.milliseconds,
+            onStep = { 1f - it },
         )
-    }
 
     companion object {
         @JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds
-
-        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
-        val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index 22d292e..a60665a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -20,14 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
 
 /**
  * Breaks down LOCKSCREEN->OCCLUDED transition into discrete steps for corresponding views to
@@ -39,33 +35,28 @@
 constructor(
     private val interactor: KeyguardTransitionInteractor,
 ) {
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_OCCLUDED_DURATION,
+            transitionFlow = interactor.lockscreenToOccludedTransition,
+        )
+
+    /** Lockscreen views alpha */
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 250.milliseconds,
+            onStep = { 1f - it },
+        )
 
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
-        return merge(
-            flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-                (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
-            },
-            // On end, reset the translation to 0
-            interactor.lockscreenToOccludedTransition
-                .filter { step -> step.transitionState == TransitionState.FINISHED }
-                .map { 0f }
+        return transitionAnimation.createFlow(
+            duration = TO_OCCLUDED_DURATION,
+            onStep = { value -> value * translatePx },
+            // Reset on cancel or finish
+            onFinish = { 0f },
+            onCancel = { 0f },
+            interpolator = EMPHASIZED_ACCELERATE,
         )
     }
-
-    /** Lockscreen views alpha */
-    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
-    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
-        return interactor.transitionStepAnimation(
-            interactor.lockscreenToOccludedTransition,
-            params,
-            totalDuration = TO_OCCLUDED_DURATION
-        )
-    }
-
-    companion object {
-        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_OCCLUDED_DURATION)
-        val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index e804562..5770f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -20,11 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
 
 /**
  * Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -36,28 +35,26 @@
 constructor(
     private val interactor: KeyguardTransitionInteractor,
 ) {
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_LOCKSCREEN_DURATION,
+            transitionFlow = interactor.occludedToLockscreenTransition,
+        )
+
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
-        return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-            -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
-        }
-    }
-
-    /** Lockscreen views alpha */
-    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
-
-    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
-        return interactor.transitionStepAnimation(
-            interactor.occludedToLockscreenTransition,
-            params,
-            totalDuration = TO_LOCKSCREEN_DURATION
+        return transitionAnimation.createFlow(
+            duration = TO_LOCKSCREEN_DURATION,
+            onStep = { value -> -translatePx + value * translatePx },
+            interpolator = EMPHASIZED_DECELERATE,
         )
     }
 
-    companion object {
-        @JvmField val LOCKSCREEN_ANIMATION_DURATION_MS = TO_LOCKSCREEN_DURATION.inWholeMilliseconds
-        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
-        val LOCKSCREEN_ALPHA =
-            AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
-    }
+    /** Lockscreen views alpha */
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            startTime = 233.milliseconds,
+            duration = 250.milliseconds,
+            onStep = { it },
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index be18cbe..b7a2522 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -92,6 +92,9 @@
 
     /** Whether explicit indicator exists */
     val isExplicit: Boolean = false,
+
+    /** Track progress (0 - 1) to display for players where [resumption] is true */
+    val resumeProgress: Double? = null,
 ) {
     companion object {
         /** Media is playing on the local device */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index bba5f35..a057c9f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -238,6 +238,24 @@
     }
 
     /**
+     * Set the progress to a fixed percentage value that cannot be changed by the user.
+     *
+     * @param percent value between 0 and 1
+     */
+    fun updateStaticProgress(percent: Double) {
+        val position = (percent * 100).toInt()
+        _data =
+            Progress(
+                enabled = true,
+                seekAvailable = false,
+                playing = false,
+                scrubbing = false,
+                elapsedTime = position,
+                duration = 100,
+            )
+    }
+
+    /**
      * Puts the seek bar into a resumption state.
      *
      * This should be called when the media session behind the controller has been destroyed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index da2164e..16a2e3f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -68,6 +68,7 @@
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
 import com.android.systemui.media.controls.resume.MediaResumeListener
 import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.ActivityStarter
@@ -668,6 +669,11 @@
                 MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
                 mediaFlags.isExplicitIndicatorEnabled()
 
+        val progress =
+            if (mediaFlags.isResumeProgressEnabled()) {
+                MediaDataUtils.getDescriptionProgress(desc.extras)
+            } else null
+
         val mediaAction = getResumeMediaAction(resumeAction)
         val lastActive = systemClock.elapsedRealtime()
         foregroundExecutor.execute {
@@ -698,6 +704,7 @@
                     instanceId = instanceId,
                     appUid = appUid,
                     isExplicit = isExplicit,
+                    resumeProgress = progress,
                 )
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 2fd4f27..61cc619 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -116,8 +116,6 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.time.SystemClock;
 
-import dagger.Lazy;
-
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
@@ -125,6 +123,7 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
 import kotlin.Triple;
 import kotlin.Unit;
 
@@ -524,8 +523,13 @@
         }
 
         // Seek Bar
-        final MediaController controller = getController();
-        mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
+        if (data.getResumption() && data.getResumeProgress() != null) {
+            double progress = data.getResumeProgress();
+            mSeekBarViewModel.updateStaticProgress(progress);
+        } else {
+            final MediaController controller = getController();
+            mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
+        }
 
         // Show the broadcast dialog button only when the le audio is enabled.
         mShowBroadcastDialogButton =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
index bcfceaa..85282a1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
@@ -19,8 +19,12 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Bundle;
 import android.text.TextUtils;
 
+import androidx.core.math.MathUtils;
+import androidx.media.utils.MediaConstants;
+
 /**
  * Utility class with common methods for media controls
  */
@@ -50,4 +54,35 @@
                         : unknownName);
         return applicationName;
     }
+
+    /**
+     * Check the bundle for extras indicating the progress percentage
+     *
+     * @param extras
+     * @return the progress value between 0-1 inclusive if prsent, otherwise null
+     */
+    public static Double getDescriptionProgress(Bundle extras) {
+        if (!extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS)) {
+            return null;
+        }
+
+        int status = extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS);
+        switch (status) {
+            case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED:
+                return 0.0;
+            case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED:
+                return 1.0;
+            case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED: {
+                if (extras
+                        .containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE)) {
+                    double percent = extras
+                            .getDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE);
+                    return MathUtils.clamp(percent, 0.0, 1.0);
+                } else {
+                    return 0.5;
+                }
+            }
+        }
+        return null;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 81efa36..a689dc3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -55,4 +55,7 @@
     /** Check whether we show the updated recommendation card. */
     fun isRecommendationCardUpdateEnabled() =
         featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
+
+    /** Check whether to get progress information for resume players */
+    fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index d6c6a81..e57b169 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.media.dialog;
 
-import static android.media.RouteListingPreference.Item.DISABLE_REASON_SUBSCRIPTION_REQUIRED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -411,10 +411,10 @@
     @RequiresApi(34)
     private static class Api34Impl {
         @DoNotInline
-        static String composeDisabledReason(@RouteListingPreference.Item.DisableReason int reason,
-                Context context) {
+        static String composeDisabledReason(
+                @RouteListingPreference.Item.SubText int reason, Context context) {
             switch(reason) {
-                case DISABLE_REASON_SUBSCRIPTION_REQUIRED:
+                case SUBTEXT_SUBSCRIPTION_REQUIRED:
                     return context.getString(R.string.media_output_status_require_premium);
             }
             return "";
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
index 1324d2c..c4a1ed4 100644
--- a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
@@ -19,7 +19,6 @@
 import android.view.WindowManagerGlobal
 import com.android.app.motiontool.DdmHandleMotionTool
 import com.android.app.motiontool.MotionToolManager
-import com.android.app.viewcapture.ViewCapture
 import com.android.systemui.CoreStartable
 import dagger.Binds
 import dagger.Module
@@ -38,17 +37,12 @@
         }
 
         @Provides
-        fun provideMotionToolManager(
-            viewCapture: ViewCapture,
-            windowManagerGlobal: WindowManagerGlobal
-        ): MotionToolManager {
-            return MotionToolManager.getInstance(viewCapture, windowManagerGlobal)
+        fun provideMotionToolManager(windowManagerGlobal: WindowManagerGlobal): MotionToolManager {
+            return MotionToolManager.getInstance(windowManagerGlobal)
         }
 
         @Provides
         fun provideWindowManagerGlobal(): WindowManagerGlobal = WindowManagerGlobal.getInstance()
-
-        @Provides fun provideViewCapture(): ViewCapture = ViewCapture.getInstance()
     }
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index 66b7842..1b728b8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -67,10 +67,7 @@
             }
 
             // If label wasn't loaded, use a default
-            val badgedLabel =
-                packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle)
-
-            return WorkProfileFirstRunData(badgedLabel, badgedIcon)
+            return WorkProfileFirstRunData(label ?: defaultFileAppName(), badgedIcon)
         }
         return null
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 479510a..2ac7f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3151,17 +3151,11 @@
         }
         // The padding on this area is large enough that we can use a cheaper clipping strategy
         mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null);
-        if (!qsVisible && mSplitShadeEnabled) {
-            // On the lockscreen when qs isn't visible, we don't want the bounds of the shade to
-            // be visible, otherwise you can see the bounds once swiping up to see bouncer
-            mScrimController.setNotificationsBounds(0, 0, 0, 0);
-        } else {
-            // Increase the height of the notifications scrim when not in split shade
-            // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
-            // in this case they are rendered off-screen
-            final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
-            mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
-        }
+        // Increase the height of the notifications scrim when not in split shade
+        // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
+        // in this case they are rendered off-screen
+        final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
+        mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
 
         if (mSplitShadeEnabled) {
             mKeyguardStatusBarViewController.setNoTopClipping();
@@ -3222,6 +3216,12 @@
     private int calculateQsBottomPosition(float qsExpansionFraction) {
         if (mTransitioningToFullShadeProgress > 0.0f) {
             return mTransitionToFullShadeQSPosition;
+        } else if (mSplitShadeEnabled) {
+            // in split shade - outside lockscreen transition handled above - we simply jump between
+            // two qs expansion values - either shade is closed and qs expansion is 0 or shade is
+            // open and qs expansion is 1
+            int qsBottomTarget = mQs.getDesiredHeight() + mLargeScreenShadeHeaderHeight;
+            return qsExpansionFraction > 0 ? qsBottomTarget : 0;
         } else {
             int qsBottomYFrom = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
             int expandedTopMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : 0;
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index 393279b..641131e 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -44,6 +44,11 @@
         const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition"
 
         /**
+         * The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd).
+         */
+        const val DATE_SMARTSPACE_DATA_PLUGIN = "date_smartspace_data_plugin"
+
+        /**
          * The BcSmartspaceDataPlugin for the standalone weather.
          */
         const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 5b62d30..4d7496d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.regionsampling.RegionSampler
 import com.android.systemui.shared.regionsampling.UpdateColorCallback
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -84,6 +85,8 @@
         @Main private val uiExecutor: Executor,
         @Background private val bgExecutor: Executor,
         @Main private val handler: Handler,
+        @Named(DATE_SMARTSPACE_DATA_PLUGIN)
+        optionalDatePlugin: Optional<BcSmartspaceDataPlugin>,
         @Named(WEATHER_SMARTSPACE_DATA_PLUGIN)
         optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
         optionalPlugin: Optional<BcSmartspaceDataPlugin>,
@@ -94,6 +97,7 @@
     }
 
     private var session: SmartspaceSession? = null
+    private val datePlugin: BcSmartspaceDataPlugin? = optionalDatePlugin.orElse(null)
     private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
     private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null)
@@ -223,7 +227,7 @@
         execution.assertIsMainThread()
 
         return featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) &&
-                weatherPlugin != null
+                datePlugin != null && weatherPlugin != null
     }
 
     private fun updateBypassEnabled() {
@@ -232,6 +236,25 @@
     }
 
     /**
+     * Constructs the date view and connects it to the smartspace service.
+     */
+    fun buildAndConnectDateView(parent: ViewGroup): View? {
+        execution.assertIsMainThread()
+
+        if (!isEnabled()) {
+            throw RuntimeException("Cannot build view when not enabled")
+        }
+        if (!isDateWeatherDecoupled()) {
+            throw RuntimeException("Cannot build date view when not decoupled")
+        }
+
+        val view = buildView(parent, datePlugin)
+        connectSession()
+
+        return view
+    }
+
+    /**
      * Constructs the weather view and connects it to the smartspace service.
      */
     fun buildAndConnectWeatherView(parent: ViewGroup): View? {
@@ -309,7 +332,7 @@
     }
 
     private fun connectSession() {
-        if (weatherPlugin == null && plugin == null) return
+        if (datePlugin == null && weatherPlugin == null && plugin == null) return
         if (session != null || smartspaceViews.isEmpty()) {
             return
         }
@@ -347,6 +370,7 @@
         statusBarStateController.addCallback(statusBarStateListener)
         bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
 
+        datePlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
         weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
         plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
 
@@ -384,6 +408,8 @@
         bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
         session = null
 
+        datePlugin?.registerSmartspaceEventNotifier(null)
+
         weatherPlugin?.registerSmartspaceEventNotifier(null)
         weatherPlugin?.onTargetsAvailable(emptyList())
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9275e2b..a6b71dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -591,6 +591,7 @@
         }
         mShowingPublicInitialized = false;
         updateNotificationColor();
+        updateLongClickable();
         if (mMenuRow != null) {
             mMenuRow.onNotificationUpdated(mEntry.getSbn());
             mMenuRow.setAppName(mAppName);
@@ -1196,8 +1197,26 @@
         return getShowingLayout().getVisibleWrapper();
     }
 
+    private boolean isNotificationRowLongClickable() {
+        if (mLongPressListener == null) {
+            return false;
+        }
+
+        if (!areGutsExposed()) { // guts is not opened
+            return true;
+        }
+
+        // if it is leave behind, it shouldn't be long clickable.
+        return !isGutsLeaveBehind();
+    }
+
+    private void updateLongClickable() {
+        setLongClickable(isNotificationRowLongClickable());
+    }
+
     public void setLongPressListener(LongPressListener longPressListener) {
         mLongPressListener = longPressListener;
+        updateLongClickable();
     }
 
     public void setDragController(ExpandableNotificationRowDragController dragController) {
@@ -2044,11 +2063,13 @@
     void onGutsOpened() {
         resetTranslation();
         updateContentAccessibilityImportanceForGuts(false /* isEnabled */);
+        updateLongClickable();
     }
 
     void onGutsClosed() {
         updateContentAccessibilityImportanceForGuts(true /* isEnabled */);
         mIsSnoozed = false;
+        updateLongClickable();
     }
 
     /**
@@ -2947,6 +2968,10 @@
         return (mGuts != null && mGuts.isExposed());
     }
 
+    private boolean isGutsLeaveBehind() {
+        return (mGuts != null && mGuts.isLeavebehind());
+    }
+
     @Override
     public boolean isContentExpandable() {
         if (mIsSummaryWithChildren && !shouldShowPublic()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 37ff11d..efcbb3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -586,7 +586,9 @@
         }
 
         final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
-        view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        if (view.isLongClickable()) {
+            view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        }
         if (row.areGutsExposed()) {
             closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
                     true /* removeControls */, -1 /* x */, -1 /* y */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 344d233..c1c6c88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.view.ViewStub;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.LockIconView;
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
@@ -67,6 +68,7 @@
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.settings.SecureSettings;
@@ -299,7 +301,9 @@
             OperatorNameViewController.Factory operatorNameViewControllerFactory,
             SecureSettings secureSettings,
             @Main Executor mainExecutor,
-            DumpManager dumpManager
+            DumpManager dumpManager,
+            StatusBarWindowStateController statusBarWindowStateController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor
     ) {
         return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory,
                 ongoingCallController,
@@ -320,7 +324,9 @@
                 operatorNameViewControllerFactory,
                 secureSettings,
                 mainExecutor,
-                dumpManager);
+                dumpManager,
+                statusBarWindowStateController,
+                keyguardUpdateMonitor);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index cbd27cf..65becf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -44,6 +44,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
@@ -72,6 +73,8 @@
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListener;
 import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
@@ -129,6 +132,8 @@
     private final SecureSettings mSecureSettings;
     private final Executor mMainExecutor;
     private final DumpManager mDumpManager;
+    private final StatusBarWindowStateController mStatusBarWindowStateController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     private List<String> mBlockedIcons = new ArrayList<>();
     private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
@@ -164,6 +169,22 @@
                 }
             };
 
+    /**
+     * Whether we've launched the secure camera over the lockscreen, but haven't yet received a
+     * status bar window state change afterward.
+     *
+     * We wait for this state change (which will tell us whether to show/hide the status bar icons)
+     * so that there is no flickering/jump cutting during the camera launch.
+     */
+    private boolean mWaitingForWindowStateChangeAfterCameraLaunch = false;
+
+    /**
+     * Listener that updates {@link #mWaitingForWindowStateChangeAfterCameraLaunch} when it receives
+     * a new status bar window state.
+     */
+    private final StatusBarWindowStateListener mStatusBarWindowStateListener = state ->
+            mWaitingForWindowStateChangeAfterCameraLaunch = false;
+
     @SuppressLint("ValidFragment")
     public CollapsedStatusBarFragment(
             StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
@@ -185,7 +206,9 @@
             OperatorNameViewController.Factory operatorNameViewControllerFactory,
             SecureSettings secureSettings,
             @Main Executor mainExecutor,
-            DumpManager dumpManager
+            DumpManager dumpManager,
+            StatusBarWindowStateController statusBarWindowStateController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor
     ) {
         mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
         mOngoingCallController = ongoingCallController;
@@ -207,6 +230,20 @@
         mSecureSettings = secureSettings;
         mMainExecutor = mainExecutor;
         mDumpManager = dumpManager;
+        mStatusBarWindowStateController = statusBarWindowStateController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
     }
 
     @Override
@@ -254,6 +291,11 @@
         mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
     }
 
+    @Override
+    public void onCameraLaunchGestureDetected(int source) {
+        mWaitingForWindowStateChangeAfterCameraLaunch = true;
+    }
+
     @VisibleForTesting
     void updateBlockedIcons() {
         mBlockedIcons.clear();
@@ -466,6 +508,27 @@
                 && mNotificationPanelViewController.hideStatusBarIconsWhenExpanded()) {
             return true;
         }
+
+        // When launching the camera over the lockscreen, the icons become visible momentarily
+        // before animating out, since we're not yet aware that the launching camera activity is
+        // fullscreen. Even once the activity finishes launching, it takes a short time before WM
+        // decides that the top app wants to hide the icons and tells us to hide them. To ensure
+        // that this high-visibility animation is smooth, keep the icons hidden during a camera
+        // launch until we receive a window state change which indicates that the activity is done
+        // launching and WM has decided to show/hide the icons. For extra safety (to ensure the
+        // icons don't remain hidden somehow) we double check that the camera is still showing, the
+        // status bar window isn't hidden, and we're still occluded as well, though these checks
+        // are typically unnecessary.
+        final boolean hideIconsForSecureCamera =
+                (mWaitingForWindowStateChangeAfterCameraLaunch ||
+                        !mStatusBarWindowStateController.windowIsShowing()) &&
+                        mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard() &&
+                        mKeyguardStateController.isOccluded();
+
+        if (hideIconsForSecureCamera) {
+            return true;
+        }
+
         return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index cc0ec54..b1e2812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -77,6 +77,17 @@
         return binding.getShouldIconBeVisible()
     }
 
+    /** See [StatusBarIconView.getDrawingRect]. */
+    override fun getDrawingRect(outRect: Rect) {
+        super.getDrawingRect(outRect)
+        val translationX = translationX.toInt()
+        val translationY = translationY.toInt()
+        outRect.left += translationX
+        outRect.right += translationX
+        outRect.top += translationY
+        outRect.bottom += translationY
+    }
+
     /**
      * Initializes this view.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
index 60f6df6..8f424b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
@@ -61,6 +61,10 @@
         listeners.add(listener)
     }
 
+    fun removeListener(listener: StatusBarWindowStateListener) {
+        listeners.remove(listener)
+    }
+
     /** Returns true if the window is currently showing. */
     fun windowIsShowing() = windowState == WINDOW_STATE_SHOWING
 
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index ec6965a..899b0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -80,6 +80,26 @@
         )
     }
 
+    /** Logs that there was a failure to animate the view in. */
+    fun logAnimateInFailure() {
+        buffer.log(
+            tag,
+            LogLevel.WARNING,
+            {},
+            { "View's appearance animation failed. Forcing view display manually." },
+        )
+    }
+
+    /** Logs that there was a failure to animate the view out. */
+    fun logAnimateOutFailure() {
+        buffer.log(
+            tag,
+            LogLevel.WARNING,
+            {},
+            { "View's disappearance animation failed." },
+        )
+    }
+
     fun logViewHidden(info: T) {
         buffer.log(
             tag,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
new file mode 100644
index 0000000..01a81de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay.chipbar
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.children
+import javax.inject.Inject
+
+/**
+ * A class controlling chipbar animations. Typically delegates to [ViewHierarchyAnimator].
+ *
+ * Used so that animations can be mocked out in tests.
+ */
+@SysUISingleton
+open class ChipbarAnimator @Inject constructor() {
+    /**
+     * Animates [innerView] and its children into view.
+     *
+     * @return true if the animation was successfully started and false if the animation can't be
+     * run for any reason.
+     *
+     * See [ViewHierarchyAnimator.animateAddition].
+     */
+    open fun animateViewIn(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+        return ViewHierarchyAnimator.animateAddition(
+            innerView,
+            ViewHierarchyAnimator.Hotspot.TOP,
+            Interpolators.EMPHASIZED_DECELERATE,
+            duration = ANIMATION_IN_DURATION,
+            includeMargins = true,
+            includeFadeIn = true,
+            onAnimationEnd = onAnimationEnd,
+        )
+    }
+
+    /**
+     * Animates [innerView] and its children out of view.
+     *
+     * @return true if the animation was successfully started and false if the animation can't be
+     * run for any reason.
+     *
+     * See [ViewHierarchyAnimator.animateRemoval].
+     */
+    open fun animateViewOut(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+        return ViewHierarchyAnimator.animateRemoval(
+            innerView,
+            ViewHierarchyAnimator.Hotspot.TOP,
+            Interpolators.EMPHASIZED_ACCELERATE,
+            ANIMATION_OUT_DURATION,
+            includeMargins = true,
+            onAnimationEnd,
+        )
+    }
+
+    /** Force shows this view and all child views. Should be used in case [animateViewIn] fails. */
+    fun forceDisplayView(innerView: View) {
+        innerView.alpha = 1f
+        if (innerView is ViewGroup) {
+            innerView.children.forEach { forceDisplayView(it) }
+        }
+    }
+}
+
+private const val ANIMATION_IN_DURATION = 500L
+private const val ANIMATION_OUT_DURATION = 250L
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 46f13cc..696134c 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -32,8 +32,6 @@
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.animation.ViewHierarchyAnimator
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text.Companion.loadText
@@ -78,6 +76,7 @@
     configurationController: ConfigurationController,
     dumpManager: DumpManager,
     powerManager: PowerManager,
+    private val chipbarAnimator: ChipbarAnimator,
     private val falsingManager: FalsingManager,
     private val falsingCollector: FalsingCollector,
     private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler?,
@@ -206,23 +205,17 @@
     }
 
     override fun animateViewIn(view: ViewGroup) {
+        // We can only request focus once the animation finishes.
         val onAnimationEnd = Runnable {
             maybeGetAccessibilityFocus(view.getTag(INFO_TAG) as ChipbarInfo?, view)
         }
-        val added =
-            ViewHierarchyAnimator.animateAddition(
-                view.getInnerView(),
-                ViewHierarchyAnimator.Hotspot.TOP,
-                Interpolators.EMPHASIZED_DECELERATE,
-                duration = ANIMATION_IN_DURATION,
-                includeMargins = true,
-                includeFadeIn = true,
-                // We can only request focus once the animation finishes.
-                onAnimationEnd = onAnimationEnd,
-            )
-        // If the view doesn't get animated, the [onAnimationEnd] runnable won't get run. So, just
-        // run it immediately.
-        if (!added) {
+        val animatedIn = chipbarAnimator.animateViewIn(view.getInnerView(), onAnimationEnd)
+
+        // If the view doesn't get animated, the [onAnimationEnd] runnable won't get run and the
+        // views would remain un-displayed. So, just force-set/run those items immediately.
+        if (!animatedIn) {
+            logger.logAnimateInFailure()
+            chipbarAnimator.forceDisplayView(view.getInnerView())
             onAnimationEnd.run()
         }
     }
@@ -230,18 +223,11 @@
     override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
         val innerView = view.getInnerView()
         innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
-        val removed =
-            ViewHierarchyAnimator.animateRemoval(
-                innerView,
-                ViewHierarchyAnimator.Hotspot.TOP,
-                Interpolators.EMPHASIZED_ACCELERATE,
-                ANIMATION_OUT_DURATION,
-                includeMargins = true,
-                onAnimationEnd,
-            )
+        val removed = chipbarAnimator.animateViewOut(innerView, onAnimationEnd)
         // If the view doesn't get animated, the [onAnimationEnd] runnable won't get run. So, just
         // run it immediately.
         if (!removed) {
+            logger.logAnimateOutFailure()
             onAnimationEnd.run()
         }
 
@@ -299,8 +285,6 @@
     }
 }
 
-private const val ANIMATION_IN_DURATION = 500L
-private const val ANIMATION_OUT_DURATION = 250L
 @IdRes private val INFO_TAG = R.id.tag_chipbar_info
 private const val SWIPE_UP_GESTURE_REASON = "SWIPE_UP_GESTURE_DETECTED"
 private const val TAG = "ChipbarCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9f6e602..307787b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -356,13 +356,22 @@
     };
 
     @Inject
-    public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
-            @Background Handler bgHandler, @Main Executor mainExecutor,
-            @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
-            SecureSettings secureSettings, WallpaperManager wallpaperManager,
-            UserManager userManager, DeviceProvisionedController deviceProvisionedController,
-            UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags,
-            @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) {
+    public ThemeOverlayController(
+            Context context,
+            BroadcastDispatcher broadcastDispatcher,
+            @Background Handler bgHandler,
+            @Main Executor mainExecutor,
+            @Background Executor bgExecutor,
+            ThemeOverlayApplier themeOverlayApplier,
+            SecureSettings secureSettings,
+            WallpaperManager wallpaperManager,
+            UserManager userManager,
+            DeviceProvisionedController deviceProvisionedController,
+            UserTracker userTracker,
+            DumpManager dumpManager,
+            FeatureFlags featureFlags,
+            @Main Resources resources,
+            WakefulnessLifecycle wakefulnessLifecycle) {
         mContext = context;
         mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
         mDeviceProvisionedController = deviceProvisionedController;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index a4180fd..512c351 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -33,6 +33,7 @@
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
@@ -118,6 +119,11 @@
     @Mock
     private LogBuffer mLogBuffer;
 
+    private final View mFakeDateView = (View) (new ViewGroup(mContext) {
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {}
+    });
+    private final View mFakeWeatherView = new View(mContext);
     private final View mFakeSmartspaceView = new View(mContext);
 
     private KeyguardClockSwitchController mController;
@@ -145,6 +151,8 @@
         when(mLargeClockView.getContext()).thenReturn(getContext());
 
         when(mView.isAttachedToWindow()).thenReturn(true);
+        when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
+        when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
         mExecutor = new FakeExecutor(new FakeSystemClock());
         mController = new KeyguardClockSwitchController(
@@ -252,6 +260,19 @@
     }
 
     @Test
+    public void onLocaleListChanged_rebuildsSmartspaceViews_whenDecouplingEnabled() {
+        when(mSmartspaceController.isEnabled()).thenReturn(true);
+        when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
+        mController.init();
+
+        mController.onLocaleListChanged();
+        // Should be called once on initial setup, then once again for locale change
+        verify(mSmartspaceController, times(2)).buildAndConnectDateView(mView);
+        verify(mSmartspaceController, times(2)).buildAndConnectWeatherView(mView);
+        verify(mSmartspaceController, times(2)).buildAndConnectView(mView);
+    }
+
+    @Test
     public void testSmartspaceDisabledShowsKeyguardStatusArea() {
         when(mSmartspaceController.isEnabled()).thenReturn(false);
         mController.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index fd931b0..b5c0f20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -52,7 +52,6 @@
 import androidx.test.filters.SmallTest
 import com.airbnb.lottie.LottieAnimationView
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.ViewMediatorCallback
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.SysuiTestableContext
@@ -61,9 +60,8 @@
 import com.android.systemui.flags.Flags.MODERN_ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -111,7 +109,7 @@
     @Captor lateinit var overlayCaptor: ArgumentCaptor<View>
     @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
-    private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+    private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     private val featureFlags = FakeFeatureFlags()
     private val executor = FakeExecutor(FakeSystemClock())
@@ -135,13 +133,7 @@
     @Before
     fun setup() {
         featureFlags.set(MODERN_ALTERNATE_BOUNCER, true)
-        keyguardBouncerRepository =
-            KeyguardBouncerRepository(
-                mock(ViewMediatorCallback::class.java),
-                FakeSystemClock(),
-                TestCoroutineScope(),
-                mock(TableLogBuffer::class.java),
-            )
+        keyguardBouncerRepository = FakeKeyguardBouncerRepository()
         alternateBouncerInteractor =
             AlternateBouncerInteractor(
                 keyguardBouncerRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 81a6bc2..d2183b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.data.repository.BiometricRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
@@ -65,7 +66,7 @@
         allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
         MockitoAnnotations.initMocks(this)
         keyguardBouncerRepository =
-            KeyguardBouncerRepository(
+            KeyguardBouncerRepositoryImpl(
                 mock(com.android.keyguard.ViewMediatorCallback::class.java),
                 FakeSystemClock(),
                 TestCoroutineScope(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
index af46d9b..4b41537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
@@ -16,30 +16,23 @@
 
 package com.android.systemui.biometrics.udfps
 
-import android.graphics.Point
 import android.graphics.Rect
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.EllipseOverlapDetectorParams
 import com.google.common.truth.Truth.assertThat
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 import org.junit.runners.Parameterized.Parameters
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.`when` as whenEver
 
 @SmallTest
 @RunWith(Parameterized::class)
 class EllipseOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
-    val underTest = spy(EllipseOverlapDetector(neededPoints = 1))
-
-    @Before
-    fun setUp() {
-        // Use one single center point for testing, required or total number of points may change
-        whenEver(underTest.calculateSensorPoints(SENSOR))
-            .thenReturn(listOf(Point(SENSOR.centerX(), SENSOR.centerY())))
-    }
+    val underTest =
+        EllipseOverlapDetector(
+            EllipseOverlapDetectorParams(minOverlap = .4f, targetSize = .2f, stepSize = 1)
+        )
 
     @Test
     fun isGoodOverlap() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
new file mode 100644
index 0000000..2ed0346
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.ui.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link SeekBarWithIconButtonsView}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class SeekBarWithIconButtonsViewTest extends SysuiTestCase {
+
+    private ImageView mIconStart;
+    private ImageView mIconEnd;
+    private SeekBar mSeekbar;
+    private SeekBarWithIconButtonsView mIconDiscreteSliderLinearLayout;
+
+    @Before
+    public void setUp() {
+        mIconDiscreteSliderLinearLayout = new SeekBarWithIconButtonsView(mContext);
+        mIconStart = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start);
+        mIconEnd = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end);
+        mSeekbar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
+    }
+
+    @Test
+    public void setSeekBarProgressZero_startIconAndFrameDisabled() {
+        mIconDiscreteSliderLinearLayout.setProgress(0);
+
+        assertThat(mIconStart.isEnabled()).isFalse();
+        assertThat(mIconEnd.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void setSeekBarProgressMax_endIconAndFrameDisabled() {
+        mIconDiscreteSliderLinearLayout.setProgress(mSeekbar.getMax());
+
+        assertThat(mIconEnd.isEnabled()).isFalse();
+        assertThat(mIconStart.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void setSeekBarProgressMax_allIconsAndFramesEnabled() {
+        // We are using the default value for the max of seekbar.
+        // Therefore, the max value will be DEFAULT_SEEKBAR_MAX = 6.
+        mIconDiscreteSliderLinearLayout.setProgress(1);
+
+        assertThat(mIconStart.isEnabled()).isTrue();
+        assertThat(mIconEnd.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void clickIconEnd_currentProgressIsOneToMax_reachesMax() {
+        mIconDiscreteSliderLinearLayout.setProgress(mSeekbar.getMax() - 1);
+        mIconEnd.performClick();
+
+        assertThat(mSeekbar.getProgress()).isEqualTo(mSeekbar.getMax());
+    }
+
+    @Test
+    public void clickIconStart_currentProgressIsOne_reachesZero() {
+        mIconDiscreteSliderLinearLayout.setProgress(1);
+        mIconStart.performClick();
+
+        assertThat(mSeekbar.getProgress()).isEqualTo(0);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
index 2bc2ea1..03cb1db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -47,7 +47,7 @@
         MockitoAnnotations.initMocks(this)
         val testCoroutineScope = TestCoroutineScope()
         underTest =
-            KeyguardBouncerRepository(
+            KeyguardBouncerRepositoryImpl(
                 viewMediatorCallback,
                 systemClock,
                 testCoroutineScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 68fff26..28974f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
@@ -58,7 +59,7 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
         bouncerRepository =
-            KeyguardBouncerRepository(
+            KeyguardBouncerRepositoryImpl(
                 mock(ViewMediatorCallback::class.java),
                 FakeSystemClock(),
                 TestCoroutineScope(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 3a871b4..702f3763 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -437,6 +438,43 @@
         }
 
     @Test
+    fun `DOZING to GONE`() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DOZING
+            runner.startTransition(
+                testScope,
+                TransitionInfo(
+                    ownerName = "",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.DOZING,
+                    animator =
+                        ValueAnimator().apply {
+                            duration = 10
+                            interpolator = Interpolators.LINEAR
+                        },
+                )
+            )
+            runCurrent()
+            reset(mockTransitionRepository)
+
+            // WHEN biometrics succeeds with wake and unlock mode
+            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(mockTransitionRepository).startTransition(capture())
+                }
+            // THEN a transition to DOZING should occur
+            assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
+            assertThat(info.to).isEqualTo(KeyguardState.GONE)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun `GONE to DOZING`() =
         testScope.runTest {
             // GIVEN a device with AOD not available
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 7f48ea1..c5e0252 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -68,13 +68,13 @@
     @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
-    private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
+    private lateinit var underTest: PrimaryBouncerInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         DejankUtils.setImmediate(true)
-        mPrimaryBouncerInteractor =
+        underTest =
             PrimaryBouncerInteractor(
                 repository,
                 bouncerView,
@@ -94,7 +94,7 @@
 
     @Test
     fun testShow_isScrimmed() {
-        mPrimaryBouncerInteractor.show(true)
+        underTest.show(true)
         verify(repository).setOnScreenTurnedOff(false)
         verify(repository).setKeyguardAuthenticated(null)
         verify(repository).setPrimaryHide(false)
@@ -124,7 +124,7 @@
 
     @Test
     fun testHide() {
-        mPrimaryBouncerInteractor.hide()
+        underTest.hide()
         verify(falsingCollector).onBouncerHidden()
         verify(keyguardStateController).notifyBouncerShowing(false)
         verify(repository).setPrimaryShowingSoon(false)
@@ -137,7 +137,7 @@
     @Test
     fun testExpansion() {
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        mPrimaryBouncerInteractor.setPanelExpansion(0.6f)
+        underTest.setPanelExpansion(0.6f)
         verify(repository).setPanelExpansion(0.6f)
         verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
     }
@@ -146,7 +146,7 @@
     fun testExpansion_fullyShown() {
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
+        underTest.setPanelExpansion(EXPANSION_VISIBLE)
         verify(falsingCollector).onBouncerShown()
         verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
     }
@@ -155,7 +155,7 @@
     fun testExpansion_fullyHidden() {
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
+        underTest.setPanelExpansion(EXPANSION_HIDDEN)
         verify(repository).setPrimaryVisible(false)
         verify(repository).setPrimaryShow(null)
         verify(repository).setPrimaryHide(true)
@@ -167,7 +167,7 @@
     @Test
     fun testExpansion_startingToHide() {
         `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        mPrimaryBouncerInteractor.setPanelExpansion(0.1f)
+        underTest.setPanelExpansion(0.1f)
         verify(repository).setPrimaryStartingToHide(true)
         verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
     }
@@ -175,7 +175,7 @@
     @Test
     fun testShowMessage() {
         val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
-        mPrimaryBouncerInteractor.showMessage("abc", null)
+        underTest.showMessage("abc", null)
         verify(repository).setShowMessage(argCaptor.capture())
         assertThat(argCaptor.value.message).isEqualTo("abc")
     }
@@ -184,62 +184,62 @@
     fun testDismissAction() {
         val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
         val cancelAction = mock(Runnable::class.java)
-        mPrimaryBouncerInteractor.setDismissAction(onDismissAction, cancelAction)
+        underTest.setDismissAction(onDismissAction, cancelAction)
         verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
     }
 
     @Test
     fun testUpdateResources() {
-        mPrimaryBouncerInteractor.updateResources()
+        underTest.updateResources()
         verify(repository).setResourceUpdateRequests(true)
     }
 
     @Test
     fun testNotifyKeyguardAuthenticated() {
-        mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(true)
+        underTest.notifyKeyguardAuthenticated(true)
         verify(repository).setKeyguardAuthenticated(true)
     }
 
     @Test
     fun testNotifyShowedMessage() {
-        mPrimaryBouncerInteractor.onMessageShown()
+        underTest.onMessageShown()
         verify(repository).setShowMessage(null)
     }
 
     @Test
     fun testOnScreenTurnedOff() {
-        mPrimaryBouncerInteractor.onScreenTurnedOff()
+        underTest.onScreenTurnedOff()
         verify(repository).setOnScreenTurnedOff(true)
     }
 
     @Test
     fun testSetKeyguardPosition() {
-        mPrimaryBouncerInteractor.setKeyguardPosition(0f)
+        underTest.setKeyguardPosition(0f)
         verify(repository).setKeyguardPosition(0f)
     }
 
     @Test
     fun testNotifyKeyguardAuthenticatedHandled() {
-        mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedHandled()
+        underTest.notifyKeyguardAuthenticatedHandled()
         verify(repository).setKeyguardAuthenticated(null)
     }
 
     @Test
     fun testNotifyUpdatedResources() {
-        mPrimaryBouncerInteractor.notifyUpdatedResources()
+        underTest.notifyUpdatedResources()
         verify(repository).setResourceUpdateRequests(false)
     }
 
     @Test
     fun testSetBackButtonEnabled() {
-        mPrimaryBouncerInteractor.setBackButtonEnabled(true)
+        underTest.setBackButtonEnabled(true)
         verify(repository).setIsBackButtonEnabled(true)
     }
 
     @Test
     fun testStartDisappearAnimation() {
         val runnable = mock(Runnable::class.java)
-        mPrimaryBouncerInteractor.startDisappearAnimation(runnable)
+        underTest.startDisappearAnimation(runnable)
         verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
     }
 
@@ -248,42 +248,42 @@
         `when`(repository.primaryBouncerVisible.value).thenReturn(true)
         `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isTrue()
+        assertThat(underTest.isFullyShowing()).isTrue()
         `when`(repository.primaryBouncerVisible.value).thenReturn(false)
-        assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isFalse()
+        assertThat(underTest.isFullyShowing()).isFalse()
     }
 
     @Test
     fun testIsScrimmed() {
         `when`(repository.primaryBouncerScrimmed.value).thenReturn(true)
-        assertThat(mPrimaryBouncerInteractor.isScrimmed()).isTrue()
+        assertThat(underTest.isScrimmed()).isTrue()
         `when`(repository.primaryBouncerScrimmed.value).thenReturn(false)
-        assertThat(mPrimaryBouncerInteractor.isScrimmed()).isFalse()
+        assertThat(underTest.isScrimmed()).isFalse()
     }
 
     @Test
     fun testIsInTransit() {
         `when`(repository.primaryBouncerShowingSoon.value).thenReturn(true)
-        assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+        assertThat(underTest.isInTransit()).isTrue()
         `when`(repository.primaryBouncerShowingSoon.value).thenReturn(false)
-        assertThat(mPrimaryBouncerInteractor.isInTransit()).isFalse()
+        assertThat(underTest.isInTransit()).isFalse()
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+        assertThat(underTest.isInTransit()).isTrue()
     }
 
     @Test
     fun testIsAnimatingAway() {
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
-        assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isTrue()
+        assertThat(underTest.isAnimatingAway()).isTrue()
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isFalse()
+        assertThat(underTest.isAnimatingAway()).isFalse()
     }
 
     @Test
     fun testWillDismissWithAction() {
         `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
-        assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isTrue()
+        assertThat(underTest.willDismissWithAction()).isTrue()
         `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
-        assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isFalse()
+        assertThat(underTest.willDismissWithAction()).isFalse()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
new file mode 100644
index 0000000..ea7bc91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.Looper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
+    private lateinit var repository: FakeKeyguardBouncerRepository
+    @Mock private lateinit var bouncerView: BouncerView
+    @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    private lateinit var underTest: PrimaryBouncerInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        repository = FakeKeyguardBouncerRepository()
+        underTest =
+            PrimaryBouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                primaryBouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                keyguardBypassController,
+                keyguardUpdateMonitor,
+            )
+    }
+
+    @Test
+    fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
+        val isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryVisible(true)
+        repository.setPanelExpansion(0.15f)
+
+        assertThat(isInteractable()).isFalse()
+    }
+
+    @Test
+    fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
+        val isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryVisible(false)
+        repository.setPanelExpansion(0.05f)
+
+        assertThat(isInteractable()).isFalse()
+    }
+
+    @Test
+    fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
+        var isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryVisible(true)
+        repository.setPanelExpansion(0.09f)
+
+        assertThat(isInteractable()).isTrue()
+
+        repository.setPanelExpansion(0.12f)
+        assertThat(isInteractable()).isFalse()
+
+        repository.setPanelExpansion(0f)
+        assertThat(isInteractable()).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
new file mode 100644
index 0000000..a5b78b74
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardTransitionAnimationFlowTest : SysuiTestCase() {
+    private lateinit var underTest: KeyguardTransitionAnimationFlow
+    private lateinit var repository: FakeKeyguardTransitionRepository
+
+    @Before
+    fun setUp() {
+        repository = FakeKeyguardTransitionRepository()
+        underTest =
+            KeyguardTransitionAnimationFlow(
+                1000.milliseconds,
+                repository.transitions,
+            )
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun zeroDurationThrowsException() = runTest {
+        val flow = underTest.createFlow(duration = 0.milliseconds, onStep = { it })
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun startTimePlusDurationGreaterThanTransitionDurationThrowsException() = runTest {
+        val flow =
+            underTest.createFlow(
+                startTime = 300.milliseconds,
+                duration = 800.milliseconds,
+                onStep = { it }
+            )
+    }
+
+    @Test
+    fun onFinishRunsWhenSpecified() = runTest {
+        val flow =
+            underTest.createFlow(
+                duration = 100.milliseconds,
+                onStep = { it },
+                onFinish = { 10f },
+            )
+        var animationValues = collectLastValue(flow)
+        repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+        assertThat(animationValues()).isEqualTo(10f)
+    }
+
+    @Test
+    fun onCancelRunsWhenSpecified() = runTest {
+        val flow =
+            underTest.createFlow(
+                duration = 100.milliseconds,
+                onStep = { it },
+                onCancel = { 100f },
+            )
+        var animationValues = collectLastValue(flow)
+        repository.sendTransitionStep(step(0.5f, TransitionState.CANCELED))
+        assertThat(animationValues()).isEqualTo(100f)
+    }
+
+    @Test
+    fun usesStartTime() = runTest {
+        val flow =
+            underTest.createFlow(
+                startTime = 500.milliseconds,
+                duration = 500.milliseconds,
+                onStep = { it },
+            )
+        var animationValues = collectLastValue(flow)
+        repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+        assertThat(animationValues()).isEqualTo(0f)
+
+        // Should not emit a value
+        repository.sendTransitionStep(step(0.1f, TransitionState.RUNNING))
+
+        repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 0f)
+        repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 0.2f)
+        repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 0.6f)
+        repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 1f)
+    }
+
+    @Test
+    fun usesInterpolator() = runTest {
+        val flow =
+            underTest.createFlow(
+                duration = 1000.milliseconds,
+                interpolator = EMPHASIZED_ACCELERATE,
+                onStep = { it },
+            )
+        var animationValues = collectLastValue(flow)
+        repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+        assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0f))
+        repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+        assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.5f))
+        repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+        assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.6f))
+        repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+        assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.8f))
+        repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+        assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(1f))
+    }
+
+    @Test
+    fun usesOnStepToDoubleValue() = runTest {
+        val flow =
+            underTest.createFlow(
+                duration = 1000.milliseconds,
+                onStep = { it * 2 },
+            )
+        var animationValues = collectLastValue(flow)
+        repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+        assertFloat(animationValues(), 0f)
+        repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 0.6f)
+        repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 1.2f)
+        repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 1.6f)
+        repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 2f)
+    }
+
+    private fun assertFloat(actual: Float?, expected: Float) {
+        assertThat(actual!!).isWithin(0.01f).of(expected)
+    }
+
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.GONE,
+            to = KeyguardState.DREAMING,
+            value = value,
+            transitionState = state,
+            ownerName = "GoneToDreamingTransitionViewModelTest"
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 5571663..06e397d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -18,19 +18,13 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_TRANSLATION_Y
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -46,6 +40,7 @@
 class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: DreamingToLockscreenTransitionViewModel
     private lateinit var repository: FakeKeyguardTransitionRepository
+    private lateinit var transitionAnimation: KeyguardTransitionAnimationFlow
 
     @Before
     fun setUp() {
@@ -63,32 +58,18 @@
             val job =
                 underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
+            // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
+            repository.sendTransitionStep(step(0.6f))
+            // ...up to here
+            repository.sendTransitionStep(step(0.8f))
             repository.sendTransitionStep(step(1f))
 
-            // Only 3 values should be present, since the dream overlay runs for a small fraction
-            // of the overall animation time
-            assertThat(values.size).isEqualTo(3)
-            assertThat(values[0])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0f, DREAM_OVERLAY_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.3f, DREAM_OVERLAY_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.5f, DREAM_OVERLAY_TRANSLATION_Y)
-                    ) * pixels
-                )
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
 
             job.cancel()
         }
@@ -100,16 +81,18 @@
 
             val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this)
 
+            // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
             repository.sendTransitionStep(step(0.5f))
+            // ...up to here
             repository.sendTransitionStep(step(1f))
 
             // Only two values should be present, since the dream overlay runs for a small fraction
             // of the overall animation time
-            assertThat(values.size).isEqualTo(2)
-            assertThat(values[0]).isEqualTo(1f - animValue(0f, DREAM_OVERLAY_ALPHA))
-            assertThat(values[1]).isEqualTo(1f - animValue(0.1f, DREAM_OVERLAY_ALPHA))
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -121,19 +104,15 @@
 
             val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
 
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
-            // Should start running here...
             repository.sendTransitionStep(step(0.2f))
             repository.sendTransitionStep(step(0.3f))
-            // ...up to here
             repository.sendTransitionStep(step(1f))
 
-            // Only two values should be present, since the dream overlay runs for a small fraction
-            // of the overall animation time
-            assertThat(values.size).isEqualTo(2)
-            assertThat(values[0]).isEqualTo(animValue(0.2f, LOCKSCREEN_ALPHA))
-            assertThat(values[1]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -147,58 +126,27 @@
             val job =
                 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
             repository.sendTransitionStep(step(1f))
 
-            assertThat(values.size).isEqualTo(4)
-            assertThat(values[0])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[3])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(1f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
 
             job.cancel()
         }
 
-    private fun animValue(stepValue: Float, params: AnimationParams): Float {
-        val totalDuration = TO_LOCKSCREEN_DURATION
-        val startValue = (params.startTime / totalDuration).toFloat()
-
-        val multiplier = (totalDuration / params.duration).toFloat()
-        return (stepValue - startValue) * multiplier
-    }
-
-    private fun step(value: Float): TransitionStep {
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
         return TransitionStep(
             from = KeyguardState.DREAMING,
             to = KeyguardState.LOCKSCREEN,
             value = value,
-            transitionState = TransitionState.RUNNING,
+            transitionState = state,
             ownerName = "DreamingToLockscreenTransitionViewModelTest"
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 7fa204b..14c3b50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -59,20 +55,18 @@
             val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
 
             // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
             repository.sendTransitionStep(step(0.2f))
-            // ...up to here
             repository.sendTransitionStep(step(0.3f))
+            // ...up to here
             repository.sendTransitionStep(step(1f))
 
             // Only three values should be present, since the dream overlay runs for a small
-            // fraction
-            // of the overall animation time
-            assertThat(values.size).isEqualTo(3)
-            assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
-            assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
-            assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+            // fraction of the overall animation time
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -87,45 +81,19 @@
                 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
             // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
-            // ...up to here
-            repository.sendTransitionStep(step(1f))
             // And a final reset event on CANCEL
             repository.sendTransitionStep(step(0.8f, TransitionState.CANCELED))
 
-            assertThat(values.size).isEqualTo(4)
-            assertThat(values[0])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[3]).isEqualTo(0f)
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
             job.cancel()
         }
 
-    private fun animValue(stepValue: Float, params: AnimationParams): Float {
-        val totalDuration = TO_DREAMING_DURATION
-        val startValue = (params.startTime / totalDuration).toFloat()
-
-        val multiplier = (totalDuration / params.duration).toFloat()
-        return (stepValue - startValue) * multiplier
-    }
-
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 539fc2c..ed31dc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@
             val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
 
             // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
             repository.sendTransitionStep(step(0.2f))
-            // ...up to here
             repository.sendTransitionStep(step(0.3f))
+            // ...up to here
             repository.sendTransitionStep(step(1f))
 
             // Only three values should be present, since the dream overlay runs for a small
             // fraction of the overall animation time
-            assertThat(values.size).isEqualTo(3)
-            assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
-            assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
-            assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -85,47 +80,22 @@
             val job =
                 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
-            // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
-            // ...up to here
             repository.sendTransitionStep(step(1f))
             // And a final reset event on FINISHED
             repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
 
-            assertThat(values.size).isEqualTo(4)
-            assertThat(values[0])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[3]).isEqualTo(0f)
+            assertThat(values.size).isEqualTo(6)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+            // Validate finished value
+            assertThat(values[5]).isEqualTo(0f)
 
             job.cancel()
         }
 
-    private fun animValue(stepValue: Float, params: AnimationParams): Float {
-        val totalDuration = TO_DREAMING_DURATION
-        val startValue = (params.startTime / totalDuration).toFloat()
-
-        val multiplier = (totalDuration / params.duration).toFloat()
-        return (stepValue - startValue) * multiplier
-    }
-
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 759345f..458b315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@
             val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
 
             // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
             repository.sendTransitionStep(step(0.4f))
-            // ...up to here
             repository.sendTransitionStep(step(0.7f))
+            // ...up to here
             repository.sendTransitionStep(step(1f))
 
             // Only 3 values should be present, since the dream overlay runs for a small fraction
             // of the overall animation time
-            assertThat(values.size).isEqualTo(3)
-            assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
-            assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
-            assertThat(values[2]).isEqualTo(1f - animValue(0.4f, LOCKSCREEN_ALPHA))
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -86,54 +81,51 @@
                 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
             // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
             repository.sendTransitionStep(step(1f))
             // ...up to here
 
-            assertThat(values.size).isEqualTo(4)
-            assertThat(values[0])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[3])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(1f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
             job.cancel()
         }
 
-    private fun animValue(stepValue: Float, params: AnimationParams): Float {
-        val totalDuration = TO_OCCLUDED_DURATION
-        val startValue = (params.startTime / totalDuration).toFloat()
+    @Test
+    fun lockscreenTranslationYIsCanceled() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
 
-        val multiplier = (totalDuration / params.duration).toFloat()
-        return (stepValue - startValue) * multiplier
-    }
+            val pixels = 100
+            val job =
+                underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
-    private fun step(value: Float): TransitionStep {
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0f))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.3f, TransitionState.CANCELED))
+
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
+            // Cancel will reset the translation
+            assertThat(values[3]).isEqualTo(0)
+
+            job.cancel()
+        }
+
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING,
+    ): TransitionStep {
         return TransitionStep(
             from = KeyguardState.LOCKSCREEN,
             to = KeyguardState.OCCLUDED,
             value = value,
-            transitionState = TransitionState.RUNNING,
+            transitionState = state,
             ownerName = "LockscreenToOccludedTransitionViewModelTest"
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 98d292d..a36214e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -58,21 +54,19 @@
 
             val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
 
-            repository.sendTransitionStep(step(0f))
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.1f))
             // Should start running here...
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.4f))
             repository.sendTransitionStep(step(0.5f))
-            // ...up to here
             repository.sendTransitionStep(step(0.6f))
+            // ...up to here
+            repository.sendTransitionStep(step(0.8f))
             repository.sendTransitionStep(step(1f))
 
-            // Only two values should be present, since the dream overlay runs for a small fraction
-            // of the overall animation time
-            assertThat(values.size).isEqualTo(3)
-            assertThat(values[0]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
-            assertThat(values[1]).isEqualTo(animValue(0.4f, LOCKSCREEN_ALPHA))
-            assertThat(values[2]).isEqualTo(animValue(0.5f, LOCKSCREEN_ALPHA))
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -86,58 +80,27 @@
             val job =
                 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
             repository.sendTransitionStep(step(1f))
 
-            assertThat(values.size).isEqualTo(4)
-            assertThat(values[0])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[3])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(1f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
 
             job.cancel()
         }
 
-    private fun animValue(stepValue: Float, params: AnimationParams): Float {
-        val totalDuration = TO_LOCKSCREEN_DURATION
-        val startValue = (params.startTime / totalDuration).toFloat()
-
-        val multiplier = (totalDuration / params.duration).toFloat()
-        return (stepValue - startValue) * multiplier
-    }
-
-    private fun step(value: Float): TransitionStep {
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
         return TransitionStep(
             from = KeyguardState.OCCLUDED,
             to = KeyguardState.LOCKSCREEN,
             value = value,
-            transitionState = TransitionState.RUNNING,
+            transitionState = state,
             ownerName = "OccludedToLockscreenTransitionViewModelTest"
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
index c0639f3..0a5b124 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
@@ -79,7 +79,7 @@
                 USER_ID, true, APP, null, ARTIST, TITLE, null,
                 new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
                 MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
-                InstanceId.fakeInstanceId(-1), -1, false);
+                InstanceId.fakeInstanceId(-1), -1, false, null);
         mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 1ac6695..53cc78f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -644,27 +644,8 @@
                 build()
             }
         val currentTime = clock.elapsedRealtime()
-        mediaDataManager.addResumptionControls(
-            USER_ID,
-            desc,
-            Runnable {},
-            session.sessionToken,
-            APP_NAME,
-            pendingIntent,
-            PACKAGE_NAME
-        )
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        // THEN the media data indicates that it is for resumption
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(PACKAGE_NAME),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
+        addResumeControlAndLoad(desc)
+
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isTrue()
         assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -690,27 +671,8 @@
                 build()
             }
         val currentTime = clock.elapsedRealtime()
-        mediaDataManager.addResumptionControls(
-            USER_ID,
-            desc,
-            Runnable {},
-            session.sessionToken,
-            APP_NAME,
-            pendingIntent,
-            PACKAGE_NAME
-        )
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        // THEN the media data indicates that it is for resumption
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(PACKAGE_NAME),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
+        addResumeControlAndLoad(desc)
+
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isTrue()
         assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -723,6 +685,84 @@
     }
 
     @Test
+    fun testAddResumptionControls_hasPartialProgress() {
+        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+        // WHEN resumption controls are added with partial progress
+        val progress = 0.5
+        val extras =
+            Bundle().apply {
+                putInt(
+                    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+                    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+                )
+                putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress)
+            }
+        val desc =
+            MediaDescription.Builder().run {
+                setTitle(SESSION_TITLE)
+                setExtras(extras)
+                build()
+            }
+        addResumeControlAndLoad(desc)
+
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isTrue()
+        assertThat(data.resumeProgress).isEqualTo(progress)
+    }
+
+    @Test
+    fun testAddResumptionControls_hasNotPlayedProgress() {
+        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+        // WHEN resumption controls are added that have not been played
+        val extras =
+            Bundle().apply {
+                putInt(
+                    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+                    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
+                )
+            }
+        val desc =
+            MediaDescription.Builder().run {
+                setTitle(SESSION_TITLE)
+                setExtras(extras)
+                build()
+            }
+        addResumeControlAndLoad(desc)
+
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isTrue()
+        assertThat(data.resumeProgress).isEqualTo(0)
+    }
+
+    @Test
+    fun testAddResumptionControls_hasFullProgress() {
+        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+        // WHEN resumption controls are added with progress info
+        val extras =
+            Bundle().apply {
+                putInt(
+                    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+                    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
+                )
+            }
+        val desc =
+            MediaDescription.Builder().run {
+                setTitle(SESSION_TITLE)
+                setExtras(extras)
+                build()
+            }
+        addResumeControlAndLoad(desc)
+
+        // THEN the media data includes the progress
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isTrue()
+        assertThat(data.resumeProgress).isEqualTo(1)
+    }
+
+    @Test
     fun testResumptionDisabled_dismissesResumeControls() {
         // WHEN there are resume controls and resumption is switched off
         val desc =
@@ -730,26 +770,8 @@
                 setTitle(SESSION_TITLE)
                 build()
             }
-        mediaDataManager.addResumptionControls(
-            USER_ID,
-            desc,
-            Runnable {},
-            session.sessionToken,
-            APP_NAME,
-            pendingIntent,
-            PACKAGE_NAME
-        )
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(PACKAGE_NAME),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
+        addResumeControlAndLoad(desc)
+
         val data = mediaDataCaptor.value
         mediaDataManager.setMediaResumptionEnabled(false)
 
@@ -1690,4 +1712,29 @@
         stateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 1.0f)
         whenever(controller.playbackState).thenReturn(stateBuilder.build())
     }
+
+    /** Helper function to add a resumption control and capture the resulting MediaData */
+    private fun addResumeControlAndLoad(desc: MediaDescription) {
+        mediaDataManager.addResumptionControls(
+            USER_ID,
+            desc,
+            Runnable {},
+            session.sessionToken,
+            APP_NAME,
+            pendingIntent,
+            PACKAGE_NAME
+        )
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index ce22b19..26b9204 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -906,6 +906,17 @@
     }
 
     @Test
+    fun bind_resumeState_withProgress() {
+        val progress = 0.5
+        val state = mediaData.copy(resumption = true, resumeProgress = progress)
+
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(state, PACKAGE)
+
+        verify(seekBarViewModel).updateStaticProgress(progress)
+    }
+
+    @Test
     fun bindNotificationActions() {
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
         val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 5564774..f40e3a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.media.dialog;
 
-import static android.media.RouteListingPreference.Item.DISABLE_REASON_SUBSCRIPTION_REQUIRED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -436,7 +436,7 @@
                 R.string.media_output_status_require_premium);
         when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
         when(mMediaDevice2.hasDisabledReason()).thenReturn(true);
-        when(mMediaDevice2.getDisableReason()).thenReturn(DISABLE_REASON_SUBSCRIPTION_REQUIRED);
+        when(mMediaDevice2.getDisableReason()).thenReturn(SUBTEXT_SUBSCRIPTION_REQUIRED);
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
 
         assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index c63ca3d..ef10e40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
+import com.android.systemui.temporarydisplay.chipbar.ChipbarAnimator
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
 import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
@@ -145,6 +146,7 @@
                 configurationController,
                 dumpManager,
                 powerManager,
+                ChipbarAnimator(),
                 falsingManager,
                 falsingCollector,
                 swipeHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index 576652f..3440f91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -59,9 +59,7 @@
 @RunWith(AndroidTestingRunner.class)
 public class WorkProfileMessageControllerTest extends SysuiTestCase {
     private static final String DEFAULT_LABEL = "default label";
-    private static final String BADGED_DEFAULT_LABEL = "badged default label";
     private static final String APP_LABEL = "app label";
-    private static final String BADGED_APP_LABEL = "badged app label";
     private static final UserHandle NON_WORK_USER = UserHandle.of(0);
     private static final UserHandle WORK_USER = UserHandle.of(10);
 
@@ -91,10 +89,6 @@
                 eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
                 eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
         when(mMockContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL);
-        when(mPackageManager.getUserBadgedLabel(eq(DEFAULT_LABEL), any()))
-                .thenReturn(BADGED_DEFAULT_LABEL);
-        when(mPackageManager.getUserBadgedLabel(eq(APP_LABEL), any()))
-                .thenReturn(BADGED_APP_LABEL);
         when(mPackageManager.getActivityIcon(any(ComponentName.class)))
                 .thenReturn(mActivityIcon);
         when(mPackageManager.getUserBadgedIcon(
@@ -133,7 +127,7 @@
         WorkProfileMessageController.WorkProfileFirstRunData data =
                 mMessageController.onScreenshotTaken(WORK_USER);
 
-        assertEquals(BADGED_DEFAULT_LABEL, data.getAppName());
+        assertEquals(DEFAULT_LABEL, data.getAppName());
         assertNull(data.getIcon());
     }
 
@@ -142,7 +136,7 @@
         WorkProfileMessageController.WorkProfileFirstRunData data =
                 mMessageController.onScreenshotTaken(WORK_USER);
 
-        assertEquals(BADGED_APP_LABEL, data.getAppName());
+        assertEquals(APP_LABEL, data.getAppName());
         assertEquals(mBadgedActivityIcon, data.getIcon());
     }
 
@@ -151,7 +145,7 @@
         ViewGroup layout = (ViewGroup) LayoutInflater.from(mContext).inflate(
                 R.layout.screenshot_work_profile_first_run, null);
         WorkProfileMessageController.WorkProfileFirstRunData data =
-                new WorkProfileMessageController.WorkProfileFirstRunData(BADGED_APP_LABEL,
+                new WorkProfileMessageController.WorkProfileFirstRunData(APP_LABEL,
                         mBadgedActivityIcon);
         final CountDownLatch countdown = new CountDownLatch(1);
         mMessageController.populateView(layout, data, () -> {
@@ -163,7 +157,7 @@
         assertEquals(mBadgedActivityIcon, image.getDrawable());
         TextView text = layout.findViewById(R.id.screenshot_message_content);
         // The app name is used in a template, but at least validate that it was inserted.
-        assertTrue(text.getText().toString().contains(BADGED_APP_LABEL));
+        assertTrue(text.getText().toString().contains(APP_LABEL));
 
         // Validate that clicking the dismiss button calls back properly.
         assertEquals(1, countdown.getCount());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 2423f13..0a576de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -113,6 +113,9 @@
     private lateinit var handler: Handler
 
     @Mock
+    private lateinit var datePlugin: BcSmartspaceDataPlugin
+
+    @Mock
     private lateinit var weatherPlugin: BcSmartspaceDataPlugin
 
     @Mock
@@ -155,6 +158,7 @@
         KeyguardBypassController.OnBypassStateChangedListener
     private lateinit var deviceProvisionedListener: DeviceProvisionedListener
 
+    private lateinit var dateSmartspaceView: SmartspaceView
     private lateinit var weatherSmartspaceView: SmartspaceView
     private lateinit var smartspaceView: SmartspaceView
 
@@ -190,6 +194,8 @@
         `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING))
                 .thenReturn(fakeNotifOnLockscreenSettingUri)
         `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
+        `when`(datePlugin.getView(any())).thenReturn(
+                createDateSmartspaceView(), createDateSmartspaceView())
         `when`(weatherPlugin.getView(any())).thenReturn(
                 createWeatherSmartspaceView(), createWeatherSmartspaceView())
         `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
@@ -221,6 +227,7 @@
                 executor,
                 bgExecutor,
                 handler,
+                Optional.of(datePlugin),
                 Optional.of(weatherPlugin),
                 Optional.of(plugin),
                 Optional.of(configPlugin),
@@ -275,7 +282,8 @@
 
         // THEN the listener is registered to the underlying plugin
         verify(plugin).registerListener(controllerListener)
-        // The listener is registered only for the plugin, not the weather plugin.
+        // The listener is registered only for the plugin, not the date, or weather plugin.
+        verify(datePlugin, never()).registerListener(any())
         verify(weatherPlugin, never()).registerListener(any())
     }
 
@@ -289,7 +297,8 @@
 
         // THEN the listener is subsequently registered
         verify(plugin).registerListener(controllerListener)
-        // The listener is registered only for the plugin, not the weather plugin.
+        // The listener is registered only for the plugin, not the date, or the weather plugin.
+        verify(datePlugin, never()).registerListener(any())
         verify(weatherPlugin, never()).registerListener(any())
     }
 
@@ -308,6 +317,7 @@
         verify(plugin).registerSmartspaceEventNotifier(null)
         verify(weatherPlugin).onTargetsAvailable(emptyList())
         verify(weatherPlugin).registerSmartspaceEventNotifier(null)
+        verify(datePlugin).registerSmartspaceEventNotifier(null)
     }
 
     @Test
@@ -357,6 +367,7 @@
         configChangeListener.onThemeChanged()
 
         // We update the new text color to match the wallpaper color
+        verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
         verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
         verify(smartspaceView).setPrimaryTextColor(anyInt())
     }
@@ -384,6 +395,7 @@
         statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)
 
         // We pass that along to the view
+        verify(dateSmartspaceView).setDozeAmount(0.7f)
         verify(weatherSmartspaceView).setDozeAmount(0.7f)
         verify(smartspaceView).setDozeAmount(0.7f)
     }
@@ -502,6 +514,8 @@
         verify(plugin).onTargetsAvailable(eq(listOf(targets[0], targets[1], targets[2])))
         // No filtering is applied for the weather plugin
         verify(weatherPlugin).onTargetsAvailable(eq(targets))
+        // No targets needed for the date plugin
+        verify(datePlugin, never()).onTargetsAvailable(any())
     }
 
     @Test
@@ -633,6 +647,18 @@
 
     private fun connectSession() {
         if (controller.isDateWeatherDecoupled()) {
+            val dateView = controller.buildAndConnectDateView(fakeParent)
+            dateSmartspaceView = dateView as SmartspaceView
+            fakeParent.addView(dateView)
+            controller.stateChangeListener.onViewAttachedToWindow(dateView)
+
+            verify(dateSmartspaceView).setUiSurface(
+                    BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+            verify(dateSmartspaceView).registerDataProvider(datePlugin)
+
+            verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
+            verify(dateSmartspaceView).setDozeAmount(0.5f)
+
             val weatherView = controller.buildAndConnectWeatherView(fakeParent)
             weatherSmartspaceView = weatherView as SmartspaceView
             fakeParent.addView(weatherView)
@@ -686,6 +712,7 @@
         verify(smartspaceView).setDozeAmount(0.5f)
 
         if (controller.isDateWeatherDecoupled()) {
+            clearInvocations(dateSmartspaceView)
             clearInvocations(weatherSmartspaceView)
         }
         clearInvocations(smartspaceView)
@@ -734,7 +761,38 @@
         ).thenReturn(if (value) 1 else 0)
     }
 
-    // Separate function for the weather view, which doesn't implement all functions in interface.
+    // Separate function for the date view, which implements a specific subset of all functions.
+    private fun createDateSmartspaceView(): SmartspaceView {
+        return spy(object : View(context), SmartspaceView {
+            override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
+            }
+
+            override fun setPrimaryTextColor(color: Int) {
+            }
+
+            override fun setIsDreaming(isDreaming: Boolean) {
+            }
+
+            override fun setUiSurface(uiSurface: String) {
+            }
+
+            override fun setDozeAmount(amount: Float) {
+            }
+
+            override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
+            }
+
+            override fun setFalsingManager(falsingManager: FalsingManager?) {
+            }
+
+            override fun setDnd(image: Drawable?, description: String?) {
+            }
+
+            override fun setNextAlarm(image: Drawable?, description: String?) {
+            }
+        })
+    }
+    // Separate function for the weather view, which implements a specific subset of all functions.
     private fun createWeatherSmartspaceView(): SmartspaceView {
         return spy(object : View(context), SmartspaceView {
             override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 36e76f4..85e8c34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -23,10 +23,12 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -45,6 +47,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.dump.DumpManager;
@@ -67,6 +70,8 @@
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
@@ -79,6 +84,9 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -118,6 +126,12 @@
     private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private StatusBarWindowStateController mStatusBarWindowStateController;
+    @Mock
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+    private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
 
     public CollapsedStatusBarFragmentTest() {
         super(CollapsedStatusBarFragment.class);
@@ -127,6 +141,14 @@
     public void setup() {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         mDependency.injectMockDependency(DarkIconDispatcher.class);
+
+        // Keep the window state listeners so we can dispatch to them to test the status bar
+        // fragment's response.
+        doAnswer(invocation -> {
+            mStatusBarWindowStateListeners.add(invocation.getArgument(0));
+            return null;
+        }).when(mStatusBarWindowStateController).addListener(
+                any(StatusBarWindowStateListener.class));
     }
 
     @Test
@@ -414,6 +436,27 @@
         assertFalse(contains);
     }
 
+    @Test
+    public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
+        final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+        mockSecureCameraLaunch(fragment, true /* launched */);
+
+        // Status icons should be invisible or gone, but certainly not VISIBLE.
+        assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+        mockSecureCameraLaunchFinished();
+
+        assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+        mockSecureCameraLaunch(fragment, false /* launched */);
+
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertEquals(View.VISIBLE, getClockView().getVisibility());
+    }
+
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         MockitoAnnotations.initMocks(this);
@@ -455,7 +498,9 @@
                 mOperatorNameViewControllerFactory,
                 mSecureSettings,
                 mExecutor,
-                mDumpManager);
+                mDumpManager,
+                mStatusBarWindowStateController,
+                mKeyguardUpdateMonitor);
     }
 
     private void setUpDaggerComponent() {
@@ -478,6 +523,35 @@
                 mNotificationAreaInner);
     }
 
+    /**
+     * Configure mocks to return values consistent with the secure camera animating itself launched
+     * over the keyguard.
+     */
+    private void mockSecureCameraLaunch(CollapsedStatusBarFragment fragment, boolean launched) {
+        when(mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard()).thenReturn(launched);
+        when(mKeyguardStateController.isOccluded()).thenReturn(launched);
+
+        if (launched) {
+            fragment.onCameraLaunchGestureDetected(0 /* source */);
+        } else {
+            for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) {
+                listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_SHOWING);
+            }
+        }
+
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+    }
+
+    /**
+     * Configure mocks to return values consistent with the secure camera showing over the keyguard
+     * with its launch animation finished.
+     */
+    private void mockSecureCameraLaunchFinished() {
+        for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) {
+            listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_HIDDEN);
+        }
+    }
+
     private CollapsedStatusBarFragment resumeAndGetFragment() {
         mFragments.dispatchResume();
         processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
index 3fe6983..e4c8fd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.shared.ui.view
 
+import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
@@ -118,6 +119,22 @@
         assertThat(view.isIconVisible).isEqualTo(false)
     }
 
+    @Test
+    fun getDrawingRect_takesTranslationIntoAccount() {
+        val view = createAndInitView()
+
+        view.translationX = 50f
+        view.translationY = 60f
+
+        val drawingRect = Rect()
+        view.getDrawingRect(drawingRect)
+
+        assertThat(drawingRect.left).isEqualTo(view.left + 50)
+        assertThat(drawingRect.right).isEqualTo(view.right + 50)
+        assertThat(drawingRect.top).isEqualTo(view.top + 60)
+        assertThat(drawingRect.bottom).isEqualTo(view.bottom + 60)
+    }
+
     private fun createAndInitView(): ModernStatusBarView {
         val view = ModernStatusBarView(context, null)
         binding = TestBinding()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index dd04ac4..fc7436a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -79,6 +79,7 @@
     @Mock private lateinit var viewUtil: ViewUtil
     @Mock private lateinit var vibratorHelper: VibratorHelper
     @Mock private lateinit var swipeGestureHandler: SwipeChipbarAwayGestureHandler
+    private lateinit var chipbarAnimator: TestChipbarAnimator
     private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
     private lateinit var fakeWakeLock: WakeLockFake
     private lateinit var fakeClock: FakeSystemClock
@@ -98,6 +99,7 @@
         fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
 
         uiEventLoggerFake = UiEventLoggerFake()
+        chipbarAnimator = TestChipbarAnimator()
 
         underTest =
             ChipbarCoordinator(
@@ -109,6 +111,7 @@
                 configurationController,
                 dumpManager,
                 powerManager,
+                chipbarAnimator,
                 falsingManager,
                 falsingCollector,
                 swipeGestureHandler,
@@ -371,6 +374,26 @@
         verify(vibratorHelper).vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK))
     }
 
+    /** Regression test for b/266119467. */
+    @Test
+    fun displayView_animationFailure_viewsStillBecomeVisible() {
+        chipbarAnimator.allowAnimation = false
+
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Loaded("text"),
+                endItem = ChipbarEndItem.Loading,
+            )
+        )
+
+        val view = getChipbarView()
+        assertThat(view.getInnerView().alpha).isEqualTo(1f)
+        assertThat(view.getStartIconView().alpha).isEqualTo(1f)
+        assertThat(view.getLoadingIcon().alpha).isEqualTo(1f)
+        assertThat(view.getChipTextView().alpha).isEqualTo(1f)
+    }
+
     @Test
     fun updateView_viewUpdated() {
         // First, display a view
@@ -453,6 +476,25 @@
         verify(windowManager).removeView(chipbarView)
     }
 
+    /** Regression test for b/266209420. */
+    @Test
+    fun removeView_animationFailure_viewStillRemoved() {
+        chipbarAnimator.allowAnimation = false
+
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                Text.Loaded("title text"),
+                endItem = ChipbarEndItem.Error,
+            ),
+        )
+        val chipbarView = getChipbarView()
+
+        underTest.removeView(DEVICE_ID, "test reason")
+
+        verify(windowManager).removeView(chipbarView)
+    }
+
     @Test
     fun swipeToDismiss_false_neverListensForGesture() {
         underTest.displayView(
@@ -560,8 +602,9 @@
 
     private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
 
-    private fun ViewGroup.getChipText(): String =
-        (this.requireViewById<TextView>(R.id.text)).text as String
+    private fun ViewGroup.getChipTextView() = this.requireViewById<TextView>(R.id.text)
+
+    private fun ViewGroup.getChipText(): String = this.getChipTextView().text as String
 
     private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
 
@@ -574,6 +617,25 @@
         verify(windowManager).addView(viewCaptor.capture(), any())
         return viewCaptor.value as ViewGroup
     }
+
+    /** Test class that lets us disallow animations. */
+    inner class TestChipbarAnimator : ChipbarAnimator() {
+        var allowAnimation: Boolean = true
+
+        override fun animateViewIn(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+            if (!allowAnimation) {
+                return false
+            }
+            return super.animateViewIn(innerView, onAnimationEnd)
+        }
+
+        override fun animateViewOut(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+            if (!allowAnimation) {
+                return false
+            }
+            return super.animateViewOut(innerView, onAnimationEnd)
+        }
+    }
 }
 
 private const val TIMEOUT = 10000
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
new file mode 100644
index 0000000..d0383e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [KeyguardRepository] */
+class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
+    private val _primaryBouncerVisible = MutableStateFlow(false)
+    override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
+    private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
+    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
+    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+    private val _primaryBouncerHide = MutableStateFlow(false)
+    override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
+    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
+    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+    override val primaryBouncerStartingDisappearAnimation =
+        _primaryBouncerDisappearAnimation.asStateFlow()
+    private val _primaryBouncerScrimmed = MutableStateFlow(false)
+    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+    private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
+    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+    private val _keyguardPosition = MutableStateFlow(0f)
+    override val keyguardPosition = _keyguardPosition.asStateFlow()
+    private val _onScreenTurnedOff = MutableStateFlow(false)
+    override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
+    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+    override val showMessage = _showMessage.asStateFlow()
+    private val _resourceUpdateRequests = MutableStateFlow(false)
+    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+    override val bouncerPromptReason = 0
+    override val bouncerErrorMessage: CharSequence? = null
+    private val _isAlternateBouncerVisible = MutableStateFlow(false)
+    override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
+    override var lastAlternateBouncerVisibleTime: Long = 0L
+    private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
+    override val isAlternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
+
+    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
+        _primaryBouncerScrimmed.value = isScrimmed
+    }
+
+    override fun setPrimaryVisible(isVisible: Boolean) {
+        _primaryBouncerVisible.value = isVisible
+    }
+
+    override fun setAlternateVisible(isVisible: Boolean) {
+        _isAlternateBouncerVisible.value = isVisible
+    }
+
+    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+        _isAlternateBouncerUIAvailable.value = isAvailable
+    }
+
+    override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+        _primaryBouncerShow.value = keyguardBouncerModel
+    }
+
+    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
+        _primaryBouncerShowingSoon.value = showingSoon
+    }
+
+    override fun setPrimaryHide(hide: Boolean) {
+        _primaryBouncerHide.value = hide
+    }
+
+    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
+        _primaryBouncerStartingToHide.value = startingToHide
+    }
+
+    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+        _primaryBouncerDisappearAnimation.value = runnable
+    }
+
+    override fun setPanelExpansion(panelExpansion: Float) {
+        _panelExpansionAmount.value = panelExpansion
+    }
+
+    override fun setKeyguardPosition(keyguardPosition: Float) {
+        _keyguardPosition.value = keyguardPosition
+    }
+
+    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+        _resourceUpdateRequests.value = willUpdateResources
+    }
+
+    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+        _showMessage.value = bouncerShowMessageModel
+    }
+
+    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+        _keyguardAuthenticated.value = keyguardAuthenticated
+    }
+
+    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+        _isBackButtonEnabled.value = isBackButtonEnabled
+    }
+
+    override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
+        _onScreenTurnedOff.value = onScreenTurnedOff
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index de7184c..37069dc 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -371,6 +371,7 @@
 
             final MagnificationConfig config = new MagnificationConfig.Builder()
                     .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+                    .setActivated(mMagnificationActivated)
                     .setScale(scale)
                     .setCenterX(getCenterX())
                     .setCenterY(getCenterY()).build();
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 129bc16..558c71b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -284,6 +284,7 @@
         }
         synchronized (mLock) {
             final int targetMode = config.getMode();
+            final boolean targetActivated = config.isActivated();
             final PointF currentCenter = getCurrentMagnificationCenterLocked(displayId, targetMode);
             final PointF magnificationCenter = new PointF(config.getCenterX(), config.getCenterY());
             if (currentCenter != null) {
@@ -310,20 +311,30 @@
                     : config.getScale();
             try {
                 setTransitionState(displayId, targetMode);
-
+                // Activate or deactivate target mode depending on config activated value
                 if (targetMode == MAGNIFICATION_MODE_WINDOW) {
                     screenMagnificationController.reset(displayId, false);
-                    windowMagnificationMgr.enableWindowMagnification(displayId,
-                            targetScale, magnificationCenter.x, magnificationCenter.y,
-                            animate ? STUB_ANIMATION_CALLBACK : null, id);
+                    if (targetActivated) {
+                        windowMagnificationMgr.enableWindowMagnification(displayId,
+                                targetScale, magnificationCenter.x, magnificationCenter.y,
+                                animate ? STUB_ANIMATION_CALLBACK : null, id);
+                    } else {
+                        windowMagnificationMgr.disableWindowMagnification(displayId, false);
+                    }
                 } else if (targetMode == MAGNIFICATION_MODE_FULLSCREEN) {
                     windowMagnificationMgr.disableWindowMagnification(displayId, false, null);
-                    if (!screenMagnificationController.isRegistered(displayId)) {
-                        screenMagnificationController.register(displayId);
+                    if (targetActivated) {
+                        if (!screenMagnificationController.isRegistered(displayId)) {
+                            screenMagnificationController.register(displayId);
+                        }
+                        screenMagnificationController.setScaleAndCenter(displayId, targetScale,
+                                magnificationCenter.x, magnificationCenter.y, animate,
+                                id);
+                    } else {
+                        if (screenMagnificationController.isRegistered(displayId)) {
+                            screenMagnificationController.reset(displayId, false);
+                        }
                     }
-                    screenMagnificationController.setScaleAndCenter(displayId, targetScale,
-                            magnificationCenter.x, magnificationCenter.y, animate,
-                            id);
                 }
             } finally {
                 // Reset transition state after enabling target mode.
@@ -454,6 +465,7 @@
         if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) {
             final MagnificationConfig config = new MagnificationConfig.Builder()
                     .setMode(MAGNIFICATION_MODE_WINDOW)
+                    .setActivated(getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId))
                     .setScale(getWindowMagnificationMgr().getScale(displayId))
                     .setCenterX(bounds.exactCenterX())
                     .setCenterY(bounds.exactCenterY()).build();
@@ -834,6 +846,7 @@
                                 new MagnificationConfig.Builder();
                         Region region = new Region();
                         configBuilder.setMode(MAGNIFICATION_MODE_FULLSCREEN)
+                                .setActivated(screenMagnificationController.isActivated(mDisplayId))
                                 .setScale(screenMagnificationController.getScale(mDisplayId))
                                 .setCenterX(screenMagnificationController.getCenterX(mDisplayId))
                                 .setCenterY(screenMagnificationController.getCenterY(mDisplayId));
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 75fe026..5cf2a63 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -79,6 +79,7 @@
             final FullScreenMagnificationController fullScreenMagnificationController =
                     mController.getFullScreenMagnificationController();
             builder.setMode(mode)
+                    .setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_FULLSCREEN))
                     .setScale(fullScreenMagnificationController.getScale(displayId))
                     .setCenterX(fullScreenMagnificationController.getCenterX(displayId))
                     .setCenterY(fullScreenMagnificationController.getCenterY(displayId));
@@ -86,9 +87,13 @@
             final WindowMagnificationManager windowMagnificationManager =
                     mController.getWindowMagnificationMgr();
             builder.setMode(mode)
+                    .setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_WINDOW))
                     .setScale(windowMagnificationManager.getScale(displayId))
                     .setCenterX(windowMagnificationManager.getCenterX(displayId))
                     .setCenterY(windowMagnificationManager.getCenterY(displayId));
+        } else {
+            // For undefined mode, set enabled to false
+            builder.setActivated(false);
         }
         return builder.build();
     }
@@ -118,15 +123,26 @@
         if (configMode == MAGNIFICATION_MODE_DEFAULT) {
             configMode = getControllingMode(displayId);
         }
+        // Check should activate or deactivate the target mode in config
+        boolean configActivated = config.isActivated();
         if (configMode == MAGNIFICATION_MODE_FULLSCREEN) {
-            return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(),
-                    config.getCenterX(), config.getCenterY(),
-                    animate, id);
+            if (configActivated) {
+                return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(),
+                        config.getCenterX(), config.getCenterY(),
+                        animate, id);
+            } else {
+                return resetFullscreenMagnification(displayId, animate);
+            }
         } else if (configMode == MAGNIFICATION_MODE_WINDOW) {
-            return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId,
-                    config.getScale(), config.getCenterX(), config.getCenterY(),
-                    animate ? STUB_ANIMATION_CALLBACK : null,
-                    id);
+            if (configActivated) {
+                return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId,
+                        config.getScale(), config.getCenterX(), config.getCenterY(),
+                        animate ? STUB_ANIMATION_CALLBACK : null,
+                        id);
+            } else {
+                return mController.getWindowMagnificationMgr()
+                        .disableWindowMagnification(displayId, false);
+            }
         }
         return false;
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 7591329..2d5f894 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -632,7 +632,7 @@
      * @param clear {@true} Clears the state of window magnification.
      * @return {@code true} if the magnification is turned to be disabled successfully
      */
-    boolean disableWindowMagnification(int displayId, boolean clear) {
+    public boolean disableWindowMagnification(int displayId, boolean clear) {
         return disableWindowMagnification(displayId, clear, STUB_ANIMATION_CALLBACK);
     }
 
@@ -697,7 +697,6 @@
      * @param displayId The logical display id.
      * @return {@code true} if the window magnification is enabled.
      */
-    @VisibleForTesting
     public boolean isWindowMagnifierEnabled(int displayId) {
         synchronized (mLock) {
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 5f1da7b..590f472 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -315,7 +315,12 @@
                 event.mFillRequestSentTimestampMs,
                 event.mFillResponseReceivedTimestampMs,
                 event.mSuggestionSentTimestampMs,
-                event.mSuggestionPresentedTimestampMs);
+                event.mSuggestionPresentedTimestampMs,
+                //TODO(b/265051751): add new framework logging.
+                /* selected_dataset_id= */ 0,
+                /* dialog_dismissed= */ false,
+                /* negative_cta_button_clicked= */ false,
+                /* positive_cta_button_clicked= */ false);
         mEventInternal = Optional.empty();
     }
 
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index b41664f..7a93719 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -52,6 +52,7 @@
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UEventObserver;
 import android.os.UserHandle;
@@ -574,6 +575,10 @@
                 EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
                         mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0,
                         mPlugType, mHealthInfo.batteryTechnology);
+                SystemProperties.set(
+                        "debug.tracing.battery_status",
+                        Integer.toString(mHealthInfo.batteryStatus));
+                SystemProperties.set("debug.tracing.plug_type", Integer.toString(mPlugType));
             }
             if (mHealthInfo.batteryLevel != mLastBatteryLevel) {
                 // Don't do this just from voltage or temperature changes, that is
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index f93f504..b6a2a0e 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -96,7 +96,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 /**
@@ -287,7 +286,7 @@
          * - dynamically installed mobile bundled apps (MBAs) (new in Android U)
          */
         public void recordMeasurementsForAllPackages() {
-            // check if we should record the resulting measurements
+            // check if we should measure and record
             long currentTimeMs = System.currentTimeMillis();
             if ((currentTimeMs - mMeasurementsLastRecordedMs) < RECORD_MEASUREMENTS_COOLDOWN_MS) {
                 Slog.d(TAG, "Skip measurement since the last measurement was only taken at "
@@ -1230,10 +1229,8 @@
      * JobService to measure all covered binaries and record result to Westworld.
      */
     public static class UpdateMeasurementsJobService extends JobService {
-        private static AtomicBoolean sScheduled = new AtomicBoolean();
         private static long sTimeLastRanMs = 0;
-        private static final int DO_BINARY_MEASUREMENTS_JOB_ID =
-                UpdateMeasurementsJobService.class.hashCode();
+        private static final int DO_BINARY_MEASUREMENTS_JOB_ID = 1740526926;
 
         @Override
         public boolean onStartJob(JobParameters params) {
@@ -1256,7 +1253,6 @@
                     return;
                 }
                 sTimeLastRanMs = System.currentTimeMillis();
-                sScheduled.set(false);
                 jobFinished(params, false);
             }).start();
 
@@ -1277,7 +1273,7 @@
                 return;
             }
 
-            if (sScheduled.get()) {
+            if (jobScheduler.getPendingJob(DO_BINARY_MEASUREMENTS_JOB_ID) != null) {
                 Slog.d(TAG, "A measurement job has already been scheduled.");
                 return;
             }
@@ -1303,7 +1299,6 @@
                 Slog.e(TAG, "Failed to schedule job to measure binaries.");
                 return;
             }
-            sScheduled.set(true);
             Slog.d(TAG, TextUtils.formatSimple(
                     "Job %d to measure binaries was scheduled successfully.",
                     DO_BINARY_MEASUREMENTS_JOB_ID));
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 4854c37..4b76127 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -333,6 +333,11 @@
     // Update ownership for system applications and the installers eligible to update them.
     private final ArrayMap<String, String> mUpdateOwnersForSystemApps = new ArrayMap<>();
 
+    // Set of package names that should not be marked as "stopped" during initial device boot
+    // or when adding a new user. A new package not contained in this set will be
+    // marked as stopped by the system
+    @NonNull private final Set<String> mInitialNonStoppedSystemPackages = new ArraySet<>();
+
     /**
      * Map of system pre-defined, uniquely named actors; keys are namespace,
      * value maps actor name to package name.
@@ -527,6 +532,10 @@
                 ? null : mOverlayConfigSignaturePackage;
     }
 
+    public Set<String> getInitialNonStoppedSystemPackages() {
+        return mInitialNonStoppedSystemPackages;
+    }
+
     /**
      * Only use for testing. Do NOT use in production code.
      * @param readPermissions false to create an empty SystemConfig; true to read the permissions.
@@ -1445,6 +1454,19 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "initial-package-state": {
+                        String pkgName = parser.getAttributeValue(null, "package");
+                        String stopped = parser.getAttributeValue(null, "stopped");
+                        if (TextUtils.isEmpty(pkgName)) {
+                            Slog.w(TAG, "<" + name + "> without package in " + permFile
+                                    + " at " + parser.getPositionDescription());
+                        } else if (TextUtils.isEmpty(stopped)) {
+                            Slog.w(TAG, "<" + name + "> without stopped in " + permFile
+                                    + " at " + parser.getPositionDescription());
+                        } else if (!Boolean.parseBoolean(stopped)) {
+                            mInitialNonStoppedSystemPackages.add(pkgName);
+                        }
+                    }
                     default: {
                         Slog.w(TAG, "Tag " + name + " is unknown in "
                                 + permFile + " at " + parser.getPositionDescription());
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index ae65dcb..cc8aec7 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -69,5 +69,11 @@
             ],
             "file_patterns": ["ClipboardService\\.java"]
         }
+    ],
+    "postsubmit": [
+        {
+            "name": "BinaryTransparencyHostTest",
+            "file_patterns": ["BinaryTransparencyService\\.java"]
+        }
     ]
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 57a89e3..423a090 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18974,14 +18974,20 @@
                 return null;
             }
 
-            // Starts with all displays but DEFAULT_DISPLAY
-            int[] displayIds = new int[allDisplays.length - 1];
+            boolean allowOnDefaultDisplay = UserManager
+                    .isVisibleBackgroundUsersOnDefaultDisplayEnabled();
+            int displaysSize = allDisplays.length;
+            if (!allowOnDefaultDisplay) {
+                displaysSize--;
+            }
+            int[] displayIds = new int[displaysSize];
 
-            // TODO(b/247592632): check for other properties like isSecure or proper display type
             int numberValidDisplays = 0;
             for (Display display : allDisplays) {
                 int displayId = display.getDisplayId();
-                if (display.isValid() && displayId != Display.DEFAULT_DISPLAY) {
+                // TODO(b/247592632): check other properties like isSecure or proper display type
+                if (display.isValid()
+                        && (allowOnDefaultDisplay || displayId != Display.DEFAULT_DISPLAY)) {
                     displayIds[numberValidDisplays++] = displayId;
                 }
             }
@@ -18993,14 +18999,15 @@
                 // STOPSHIP: if not removed, it should at least be unit tested
                 String testingProp = "fw.display_ids_for_starting_users_for_testing_purposes";
                 int displayId = SystemProperties.getInt(testingProp, Display.DEFAULT_DISPLAY);
-                if (displayId != Display.DEFAULT_DISPLAY && displayId > 0) {
-                    Slogf.w(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): no valid "
+                if (allowOnDefaultDisplay && displayId == Display.DEFAULT_DISPLAY
+                        || displayId > 0) {
+                    Slogf.w(TAG, "getDisplayIdsForStartingVisibleBackgroundUsers(): no valid "
                             + "display found, but returning %d as set by property %s", displayId,
                             testingProp);
                     return new int[] { displayId };
                 }
-                Slogf.e(TAG, "getDisplayIdsForStartingBackgroundUsers(): no valid display on %s",
-                        Arrays.toString(allDisplays));
+                Slogf.e(TAG, "getDisplayIdsForStartingVisibleBackgroundUsers(): no valid display"
+                        + " on %s", Arrays.toString(allDisplays));
                 return null;
             }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 88fc51a3..d888c81 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1462,7 +1462,7 @@
 
         mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
 
-        mSoundDoseHelper.configureSafeMediaVolume(/*forced=*/true, TAG);
+        mSoundDoseHelper.configureSafeMedia(/*forced=*/true, TAG);
 
         initA11yMonitoring();
 
@@ -2206,19 +2206,19 @@
                 AudioSystem.STREAM_ASSISTANT : AudioSystem.STREAM_MUSIC;
 
         if (mIsSingleVolume) {
-            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION;
+            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION.clone();
             dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
         } else if (mUseVolumeGroupAliases) {
-            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NONE;
+            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NONE.clone();
             dtmfStreamAlias = AudioSystem.STREAM_DTMF;
         } else {
             switch (mPlatformType) {
                 case AudioSystem.PLATFORM_VOICE:
-                    mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE;
+                    mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE.clone();
                     dtmfStreamAlias = AudioSystem.STREAM_RING;
                     break;
                 default:
-                    mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
+                    mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT.clone();
                     dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
             }
             if (!mNotifAliasRing) {
@@ -10236,7 +10236,7 @@
             // reading new configuration "safely" (i.e. under try catch) in case anything
             // goes wrong.
             Configuration config = context.getResources().getConfiguration();
-            mSoundDoseHelper.configureSafeMediaVolume(/*forced*/false, TAG);
+            mSoundDoseHelper.configureSafeMedia(/*forced*/false, TAG);
 
             boolean cameraSoundForced = readCameraSoundForced();
             synchronized (mSettingsLock) {
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 42031c6..ac02eb7 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -38,6 +38,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.MathUtils;
 
@@ -55,6 +56,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Safe media volume management.
@@ -77,14 +79,16 @@
     // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
     // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
     // (when user opts out).
+    // Note: when CSD calculation is enabled the state is set to SAFE_MEDIA_VOLUME_DISABLED
     private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
     private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
     private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2;  // confirmed
     private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3;  // unconfirmed
 
-    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = SAFE_MEDIA_VOLUME_MSG_START + 1;
+    private static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1;
     private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 2;
     private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 3;
+    private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 4;
 
     private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
 
@@ -100,14 +104,16 @@
     private static final int CSD_WARNING_TIMEOUT_MS_ACCUMULATION_START = -1;
     private static final int CSD_WARNING_TIMEOUT_MS_MOMENTARY_EXPOSURE = 5000;
 
+    private static final String PERSIST_CSD_RECORD_FIELD_SEPARATOR = ",";
+    private static final String PERSIST_CSD_RECORD_SEPARATOR_CHAR = "|";
+    private static final String PERSIST_CSD_RECORD_SEPARATOR = "\\|";
+
     private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
             "CSD updates");
 
     private int mMcc = 0;
 
-    private final boolean mEnableCsd;
-
-    final Object mSafeMediaVolumeStateLock = new Object();
+    private final Object mSafeMediaVolumeStateLock = new Object();
     private int mSafeMediaVolumeState;
 
     // Used when safe volume warning message display is requested by setStreamVolume(). In this
@@ -148,10 +154,18 @@
     @NonNull private final AudioHandler mAudioHandler;
     @NonNull private final ISafeHearingVolumeController mVolumeController;
 
+    private final boolean mEnableCsd;
+
     private ISoundDose mSoundDose;
+
+    private final Object mCsdStateLock = new Object();
+
+    @GuardedBy("mCsdStateLock")
     private float mCurrentCsd = 0.f;
     // dose at which the next dose reached warning occurs
+    @GuardedBy("mCsdStateLock")
     private float mNextCsdWarning = 1.0f;
+    @GuardedBy("mCsdStateLock")
     private final List<SoundDoseRecord> mDoseRecords = new ArrayList<>();
 
     private final Context mContext;
@@ -169,11 +183,16 @@
         }
 
         public void onNewCsdValue(float currentCsd, SoundDoseRecord[] records) {
+            if (!mEnableCsd) {
+                Log.w(TAG, "onNewCsdValue: csd not supported, ignoring value");
+                return;
+            }
+
             Log.i(TAG, "onNewCsdValue: " + currentCsd);
-            if (mCurrentCsd < currentCsd) {
-                // dose increase: going over next threshold?
-                if ((mCurrentCsd < mNextCsdWarning) && (currentCsd >= mNextCsdWarning)) {
-                    if (mEnableCsd) {
+            synchronized (mCsdStateLock) {
+                if (mCurrentCsd < currentCsd) {
+                    // dose increase: going over next threshold?
+                    if ((mCurrentCsd < mNextCsdWarning) && (currentCsd >= mNextCsdWarning)) {
                         if (mNextCsdWarning == 5.0f) {
                             // 500% dose repeat
                             mVolumeController.postDisplayCsdWarning(
@@ -188,17 +207,18 @@
                                     getTimeoutMsForWarning(
                                             AudioManager.CSD_WARNING_DOSE_REACHED_1X));
                         }
+                        mNextCsdWarning += 1.0f;
                     }
-                    mNextCsdWarning += 1.0f;
+                } else {
+                    // dose decrease: dropping below previous threshold of warning?
+                    if ((currentCsd < mNextCsdWarning - 1.0f) && (
+                            mNextCsdWarning >= 2.0f)) {
+                        mNextCsdWarning -= 1.0f;
+                    }
                 }
-            } else {
-                // dose decrease: dropping below previous threshold of warning?
-                if ((currentCsd < mNextCsdWarning - 1.0f) && (mNextCsdWarning >= 2.0f)) {
-                    mNextCsdWarning -= 1.0f;
-                }
+                mCurrentCsd = currentCsd;
+                updateSoundDoseRecords_l(records, currentCsd);
             }
-            mCurrentCsd = currentCsd;
-            updateSoundDoseRecords(records, currentCsd);
         }
     };
 
@@ -213,20 +233,22 @@
 
         mContext = context;
 
-        mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
-                Settings.Global.AUDIO_SAFE_VOLUME_STATE, 0);
-
         mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default);
+        if (mEnableCsd) {
+            mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
+        } else {
+            mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
+                    Settings.Global.AUDIO_SAFE_VOLUME_STATE, 0);
+        }
 
         // The default safe volume index read here will be replaced by the actual value when
-        // the mcc is read by onConfigureSafeVolume()
+        // the mcc is read by onConfigureSafeMedia()
+        // For now we use the same index for RS2 initial warning with CSD
         mSafeMediaVolumeIndex = mContext.getResources().getInteger(
                 R.integer.config_safe_media_volume_index) * 10;
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(
                 Context.ALARM_SERVICE);
-
-        initCsd();
     }
 
     float getRs2Value() {
@@ -456,8 +478,8 @@
         }
     }
 
-    /*package*/ void configureSafeMediaVolume(boolean forced, String caller) {
-        int msg = MSG_CONFIGURE_SAFE_MEDIA_VOLUME;
+    /*package*/ void configureSafeMedia(boolean forced, String caller) {
+        int msg = MSG_CONFIGURE_SAFE_MEDIA;
         mAudioHandler.removeMessages(msg);
 
         long time = 0;
@@ -507,8 +529,8 @@
 
     /*package*/ void handleMessage(Message msg) {
         switch (msg.what) {
-            case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
-                onConfigureSafeVolume((msg.arg1 == 1), (String) msg.obj);
+            case MSG_CONFIGURE_SAFE_MEDIA:
+                onConfigureSafeMedia((msg.arg1 == 1), (String) msg.obj);
                 break;
             case MSG_PERSIST_SAFE_VOLUME_STATE:
                 onPersistSafeVolumeState(msg.arg1);
@@ -519,8 +541,13 @@
                         Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs,
                         UserHandle.USER_CURRENT);
                 break;
+            case MSG_PERSIST_CSD_VALUES:
+                onPersistSoundDoseRecords();
+                break;
+            default:
+                Log.e(TAG, "Unexpected msg to handle: " + msg.what);
+                break;
         }
-
     }
 
     /*package*/ void dump(PrintWriter pw) {
@@ -540,33 +567,44 @@
 
     /*package*/void reset() {
         Log.d(TAG, "Reset the sound dose helper");
-        initCsd();
-    }
-
-    private void initCsd() {
-        synchronized (mSafeMediaVolumeStateLock) {
-            if (mEnableCsd) {
-                Log.v(TAG, "Initializing sound dose");
-
-                mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
-                mSoundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback);
-                try {
-                    if (mSoundDose != null && mSoundDose.asBinder().isBinderAlive()) {
-                        if (mCurrentCsd != 0.f) {
-                            Log.d(TAG, "Resetting the saved sound dose value " + mCurrentCsd);
-                            SoundDoseRecord[] records = mDoseRecords.toArray(
-                                    new SoundDoseRecord[0]);
-                            mSoundDose.resetCsd(mCurrentCsd, records);
-                        }
+        mSoundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback);
+        synchronized (mCsdStateLock) {
+            try {
+                if (mSoundDose != null && mSoundDose.asBinder().isBinderAlive()) {
+                    if (mCurrentCsd != 0.f) {
+                        Log.d(TAG,
+                                "Resetting the saved sound dose value " + mCurrentCsd);
+                        SoundDoseRecord[] records = mDoseRecords.toArray(
+                                new SoundDoseRecord[0]);
+                        mSoundDose.resetCsd(mCurrentCsd, records);
                     }
-                } catch (RemoteException e) {
-                    // noop
                 }
+            } catch (RemoteException e) {
+                // noop
             }
         }
     }
 
-    private void onConfigureSafeVolume(boolean force, String caller) {
+    private void initCsd() {
+        if (mEnableCsd) {
+            Log.v(TAG, "Initializing sound dose");
+
+            synchronized (mCsdStateLock) {
+                // Restore persisted values
+                mCurrentCsd = parseGlobalSettingFloat(
+                        Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE, /* defaultValue= */0.f);
+                mNextCsdWarning = parseGlobalSettingFloat(
+                        Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING, /* defaultValue= */1.f);
+                mDoseRecords.addAll(persistedStringToRecordList(
+                        mSettings.getGlobalString(mAudioService.getContentResolver(),
+                                Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS)));
+            }
+
+            reset();
+        }
+    }
+
+    private void onConfigureSafeMedia(boolean force, String caller) {
         synchronized (mSafeMediaVolumeStateLock) {
             int mcc = mContext.getResources().getConfiguration().mcc;
             if ((mMcc != mcc) || ((mMcc == 0) && force)) {
@@ -612,6 +650,10 @@
                                 /*obj=*/null), /*delay=*/0);
             }
         }
+
+        if (mEnableCsd) {
+            initCsd();
+        }
     }
 
     private int getTimeoutMsForWarning(@AudioManager.CsdWarning int csdWarning) {
@@ -702,7 +744,8 @@
         return null;
     }
 
-    private void updateSoundDoseRecords(SoundDoseRecord[] newRecords, float currentCsd) {
+    @GuardedBy("mCsdStateLock")
+    private void updateSoundDoseRecords_l(SoundDoseRecord[] newRecords, float currentCsd) {
         long totalDuration = 0;
         for (SoundDoseRecord record : newRecords) {
             Log.i(TAG, "  new record: " + record);
@@ -722,9 +765,87 @@
             }
         }
 
+        mAudioHandler.sendMessageAtTime(mAudioHandler.obtainMessage(MSG_PERSIST_CSD_VALUES,
+                /* arg1= */0, /* arg2= */0, /* obj= */null), /* delay= */0);
+
         mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration));
     }
 
+    private void onPersistSoundDoseRecords() {
+        synchronized (mCsdStateLock) {
+            mSettings.putGlobalString(mAudioService.getContentResolver(),
+                    Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE,
+                    Float.toString(mCurrentCsd));
+            mSettings.putGlobalString(mAudioService.getContentResolver(),
+                    Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING,
+                    Float.toString(mNextCsdWarning));
+            mSettings.putGlobalString(mAudioService.getContentResolver(),
+                    Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS,
+                    mDoseRecords.stream().map(
+                            SoundDoseHelper::recordToPersistedString).collect(
+                            Collectors.joining(PERSIST_CSD_RECORD_SEPARATOR_CHAR)));
+        }
+    }
+
+    private static String recordToPersistedString(SoundDoseRecord record) {
+        return record.timestamp
+                + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.duration
+                + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.value
+                + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.averageMel;
+    }
+
+    private static List<SoundDoseRecord> persistedStringToRecordList(String records) {
+        if (records == null || records.isEmpty()) {
+            return null;
+        }
+        return Arrays.stream(TextUtils.split(records, PERSIST_CSD_RECORD_SEPARATOR)).map(
+                SoundDoseHelper::persistedStringToRecord).filter(Objects::nonNull).collect(
+                Collectors.toList());
+    }
+
+    private static SoundDoseRecord persistedStringToRecord(String record) {
+        if (record == null || record.isEmpty()) {
+            return null;
+        }
+        final String[] fields = TextUtils.split(record, PERSIST_CSD_RECORD_FIELD_SEPARATOR);
+        if (fields.length != 4) {
+            Log.w(TAG, "Expecting 4 fields for a SoundDoseRecord, parsed " + fields.length);
+            return null;
+        }
+
+        final SoundDoseRecord sdRecord = new SoundDoseRecord();
+        try {
+            sdRecord.timestamp = Long.parseLong(fields[0]);
+            sdRecord.duration = Integer.parseInt(fields[1]);
+            sdRecord.value = Float.parseFloat(fields[2]);
+            sdRecord.averageMel = Float.parseFloat(fields[3]);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "Unable to parse persisted SoundDoseRecord: " + record, e);
+            return null;
+        }
+
+        return sdRecord;
+    }
+
+    private float parseGlobalSettingFloat(String audioSafeCsdCurrentValue, float defaultValue) {
+        String stringValue = mSettings.getGlobalString(mAudioService.getContentResolver(),
+                audioSafeCsdCurrentValue);
+        if (stringValue == null || stringValue.isEmpty()) {
+            Log.v(TAG, "No value stored in settings " + audioSafeCsdCurrentValue);
+            return defaultValue;
+        }
+
+        float value;
+        try {
+            value = Float.parseFloat(stringValue);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "Error parsing float from settings " + audioSafeCsdCurrentValue, e);
+            value = defaultValue;
+        }
+
+        return value;
+    }
+
     // StreamVolumeCommand contains the information needed to defer the process of
     // setStreamVolume() in case the user has to acknowledge the safe volume warning message.
     private static class StreamVolumeCommand {
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 064cd2d..ba96861 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -25,6 +25,7 @@
 import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
 import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
 import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
+import static com.android.server.devicestate.OverrideRequestController.FLAG_THERMAL_CRITICAL;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_UNKNOWN;
@@ -183,6 +184,9 @@
     ActivityTaskManagerInternal.ScreenObserver mOverrideRequestScreenObserver =
             new OverrideRequestScreenObserver();
 
+    @NonNull
+    private final DeviceStateNotificationController mDeviceStateNotificationController;
+
     public DeviceStateManagerService(@NonNull Context context) {
         this(context, DeviceStatePolicy.Provider
                 .fromResources(context.getResources())
@@ -211,6 +215,13 @@
         mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
         mBinderService = new BinderService();
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mDeviceStateNotificationController = new DeviceStateNotificationController(
+                context, mHandler,
+                () -> {
+                    synchronized (mLock) {
+                        mActiveOverride.ifPresent(mOverrideRequestController::cancelRequest);
+                    }
+                });
     }
 
     @Override
@@ -346,7 +357,8 @@
         return mBinderService;
     }
 
-    private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
+    private void updateSupportedStates(DeviceState[] supportedDeviceStates,
+            @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
         synchronized (mLock) {
             final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
 
@@ -370,7 +382,7 @@
                 return;
             }
 
-            mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
+            mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers, reason);
             updatePendingStateLocked();
 
             setRearDisplayStateLocked();
@@ -596,7 +608,7 @@
 
     @GuardedBy("mLock")
     private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
-            @OverrideRequestController.RequestStatus int status) {
+            @OverrideRequestController.RequestStatus int status, int flags) {
         if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) {
             switch (status) {
                 case STATUS_ACTIVE:
@@ -616,10 +628,19 @@
             switch (status) {
                 case STATUS_ACTIVE:
                     mActiveOverride = Optional.of(request);
+                    mDeviceStateNotificationController.showStateActiveNotificationIfNeeded(
+                            request.getRequestedState(), request.getUid());
                     break;
                 case STATUS_CANCELED:
                     if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
                         mActiveOverride = Optional.empty();
+                        mDeviceStateNotificationController.cancelNotification(
+                                request.getRequestedState());
+                        if ((flags & FLAG_THERMAL_CRITICAL) == FLAG_THERMAL_CRITICAL) {
+                            mDeviceStateNotificationController
+                                    .showThermalCriticalNotificationIfNeeded(
+                                            request.getRequestedState());
+                        }
                     }
                     break;
                 case STATUS_UNKNOWN: // same as default
@@ -700,7 +721,7 @@
         }
     }
 
-    private void requestStateInternal(int state, int flags, int callingPid,
+    private void requestStateInternal(int state, int flags, int callingPid, int callingUid,
             @NonNull IBinder token, boolean hasControlDeviceStatePermission) {
         synchronized (mLock) {
             final ProcessRecord processRecord = mProcessRecords.get(callingPid);
@@ -721,8 +742,8 @@
                         + " is not supported.");
             }
 
-            OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
-                    OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+            OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
+                    state, flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
             // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
             if (!hasControlDeviceStatePermission && mRearDisplayState != null
@@ -762,7 +783,7 @@
     }
 
     private void requestBaseStateOverrideInternal(int state, int flags, int callingPid,
-            @NonNull IBinder token) {
+            int callingUid, @NonNull IBinder token) {
         synchronized (mLock) {
             final Optional<DeviceState> deviceState = getStateLocked(state);
             if (!deviceState.isPresent()) {
@@ -782,8 +803,8 @@
                         + " token: " + token);
             }
 
-            OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
-                    OVERRIDE_REQUEST_TYPE_BASE_STATE);
+            OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
+                    state, flags, OVERRIDE_REQUEST_TYPE_BASE_STATE);
             mOverrideRequestController.addBaseStateRequest(request);
         }
     }
@@ -953,11 +974,12 @@
         @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
 
         @Override
-        public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
+        public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates,
+                @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
             if (newDeviceStates.length == 0) {
                 throw new IllegalArgumentException("Supported device states must not be empty");
             }
-            updateSupportedStates(newDeviceStates);
+            updateSupportedStates(newDeviceStates, reason);
         }
 
         @Override
@@ -1085,6 +1107,7 @@
         @Override // Binder call
         public void requestState(IBinder token, int state, int flags) {
             final int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
             // Allow top processes to request a device state change
             // If the calling process ID is not the top app, then we check if this process
             // holds a permission to CONTROL_DEVICE_STATE
@@ -1099,7 +1122,8 @@
 
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                requestStateInternal(state, flags, callingPid, token, hasControlStatePermission);
+                requestStateInternal(state, flags, callingPid, callingUid, token,
+                        hasControlStatePermission);
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
             }
@@ -1124,6 +1148,7 @@
         @Override // Binder call
         public void requestBaseStateOverride(IBinder token, int state, int flags) {
             final int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
             getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
                     "Permission required to control base state of device.");
 
@@ -1133,7 +1158,7 @@
 
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                requestBaseStateOverrideInternal(state, flags, callingPid, token);
+                requestBaseStateOverrideInternal(state, flags, callingPid, callingUid, token);
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
             }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
new file mode 100644
index 0000000..2f14998
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
@@ -0,0 +1,293 @@
+/*
+ * 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.devicestate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Manages the user-visible device state notifications.
+ */
+class DeviceStateNotificationController extends BroadcastReceiver {
+    private static final String TAG = "DeviceStateNotificationController";
+
+    @VisibleForTesting static final String INTENT_ACTION_CANCEL_STATE =
+            "com.android.server.devicestate.INTENT_ACTION_CANCEL_STATE";
+    @VisibleForTesting static final int NOTIFICATION_ID = 1;
+    @VisibleForTesting static final String CHANNEL_ID = "DeviceStateManager";
+    @VisibleForTesting static final String NOTIFICATION_TAG = "DeviceStateManager";
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final NotificationManager mNotificationManager;
+    private final PackageManager mPackageManager;
+
+    // Stores the notification title and content indexed with the device state identifier.
+    private final SparseArray<NotificationInfo> mNotificationInfos;
+
+    // The callback when a device state is requested to be canceled.
+    private final Runnable mCancelStateRunnable;
+
+    DeviceStateNotificationController(@NonNull Context context, @NonNull Handler handler,
+            @NonNull Runnable cancelStateRunnable) {
+        this(context, handler, cancelStateRunnable, getNotificationInfos(context),
+                context.getPackageManager(), context.getSystemService(NotificationManager.class));
+    }
+
+    @VisibleForTesting
+    DeviceStateNotificationController(
+            @NonNull Context context, @NonNull Handler handler,
+            @NonNull Runnable cancelStateRunnable,
+            @NonNull SparseArray<NotificationInfo> notificationInfos,
+            @NonNull PackageManager packageManager,
+            @NonNull NotificationManager notificationManager) {
+        mContext = context;
+        mHandler = handler;
+        mCancelStateRunnable = cancelStateRunnable;
+        mNotificationInfos = notificationInfos;
+        mPackageManager = packageManager;
+        mNotificationManager = notificationManager;
+        mContext.registerReceiver(
+                this,
+                new IntentFilter(INTENT_ACTION_CANCEL_STATE),
+                android.Manifest.permission.CONTROL_DEVICE_STATE,
+                mHandler,
+                Context.RECEIVER_NOT_EXPORTED);
+    }
+
+    /**
+     * Displays the ongoing notification indicating that the device state is active. Does nothing if
+     * the state does not have an active notification.
+     *
+     * @param state the active device state identifier.
+     * @param requestingAppUid the uid of the requesting app used to retrieve the app name.
+     */
+    void showStateActiveNotificationIfNeeded(int state, int requestingAppUid) {
+        NotificationInfo info = mNotificationInfos.get(state);
+        if (info == null || !info.hasActiveNotification()) {
+            return;
+        }
+        String requesterApplicationLabel = getApplicationLabel(requestingAppUid);
+        if (requesterApplicationLabel != null) {
+            showNotification(
+                    info.name, info.activeNotificationTitle,
+                    String.format(info.activeNotificationContent, requesterApplicationLabel),
+                    true /* ongoing */
+            );
+        } else {
+            Slog.e(TAG, "Cannot determine the requesting app name when showing state active "
+                    + "notification. uid=" + requestingAppUid + ", state=" + state);
+        }
+    }
+
+    /**
+     * Displays the notification indicating that the device state is canceled due to thermal
+     * critical condition. Does nothing if the state does not have a thermal critical notification.
+     *
+     * @param state the identifier of the device state being canceled.
+     */
+    void showThermalCriticalNotificationIfNeeded(int state) {
+        NotificationInfo info = mNotificationInfos.get(state);
+        if (info == null || !info.hasThermalCriticalNotification()) {
+            return;
+        }
+        showNotification(
+                info.name, info.thermalCriticalNotificationTitle,
+                info.thermalCriticalNotificationContent, false /* ongoing */
+        );
+    }
+
+    /**
+     * Cancels the notification of the corresponding device state.
+     *
+     * @param state the device state identifier.
+     */
+    void cancelNotification(int state) {
+        if (!mNotificationInfos.contains(state)) {
+            return;
+        }
+        mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
+    }
+
+    @Override
+    public void onReceive(@NonNull Context context, @Nullable Intent intent) {
+        if (intent != null) {
+            if (INTENT_ACTION_CANCEL_STATE.equals(intent.getAction())) {
+                mCancelStateRunnable.run();
+            }
+        }
+    }
+
+    /**
+     * Displays a notification with the specified name, title, and content.
+     *
+     * @param name the name of the notification.
+     * @param title the title of the notification.
+     * @param content the content of the notification.
+     * @param ongoing if true, display an ongoing (sticky) notification with a turn off button.
+     */
+    private void showNotification(
+            @NonNull String name, @NonNull String title, @NonNull String content, boolean ongoing) {
+        final NotificationChannel channel = new NotificationChannel(
+                CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH);
+        final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_lock) // TODO(b/266833171) update icons when available.
+                .setContentTitle(title)
+                .setContentText(content)
+                .setSubText(name)
+                .setLocalOnly(true)
+                .setOngoing(ongoing)
+                .setCategory(Notification.CATEGORY_SYSTEM);
+
+        if (ongoing) {
+            final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE)
+                    .setPackage(mContext.getPackageName());
+            final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                    mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
+            final Notification.Action action = new Notification.Action.Builder(
+                    null /* icon */,
+                    mContext.getString(R.string.device_state_notification_turn_off_button),
+                    pendingIntent)
+                    .build();
+            builder.addAction(action);
+        }
+
+        mNotificationManager.createNotificationChannel(channel);
+        mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
+    }
+
+    /**
+     * Loads the resources for the notifications. The device state identifiers and strings are
+     * stored in arrays. All the string arrays must have the same length and same order as the
+     * identifier array.
+     */
+    private static SparseArray<NotificationInfo> getNotificationInfos(Context context) {
+        final SparseArray<NotificationInfo> notificationInfos = new SparseArray<>();
+
+        final int[] stateIdentifiers =
+                context.getResources().getIntArray(
+                        R.array.device_state_notification_state_identifiers);
+        final String[] names =
+                context.getResources().getStringArray(R.array.device_state_notification_names);
+        final String[] activeNotificationTitles =
+                context.getResources().getStringArray(
+                        R.array.device_state_notification_active_titles);
+        final String[] activeNotificationContents =
+                context.getResources().getStringArray(
+                        R.array.device_state_notification_active_contents);
+        final String[] thermalCriticalNotificationTitles =
+                context.getResources().getStringArray(
+                        R.array.device_state_notification_thermal_titles);
+        final String[] thermalCriticalNotificationContents =
+                context.getResources().getStringArray(
+                        R.array.device_state_notification_thermal_contents);
+
+        if (stateIdentifiers.length != names.length
+                || stateIdentifiers.length != activeNotificationTitles.length
+                || stateIdentifiers.length != activeNotificationContents.length
+                || stateIdentifiers.length != thermalCriticalNotificationTitles.length
+                || stateIdentifiers.length != thermalCriticalNotificationContents.length
+        ) {
+            throw new IllegalStateException(
+                    "The length of state identifiers and notification texts must match!");
+        }
+
+        for (int i = 0; i < stateIdentifiers.length; i++) {
+            int identifier = stateIdentifiers[i];
+            if (identifier == DeviceStateManager.INVALID_DEVICE_STATE) {
+                continue;
+            }
+
+            notificationInfos.put(
+                    identifier,
+                    new NotificationInfo(
+                            names[i], activeNotificationTitles[i], activeNotificationContents[i],
+                            thermalCriticalNotificationTitles[i],
+                            thermalCriticalNotificationContents[i])
+            );
+        }
+
+        return notificationInfos;
+    }
+
+    /**
+     * A helper function to get app name (label) using the app uid.
+     *
+     * @param uid the uid of the app.
+     * @return app name (label) if found, or null otherwise.
+     */
+    @Nullable
+    private String getApplicationLabel(int uid) {
+        String packageName = mPackageManager.getNameForUid(uid);
+        try {
+            ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+                    packageName, PackageManager.ApplicationInfoFlags.of(0));
+            return appInfo.loadLabel(mPackageManager).toString();
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * A data class storing string resources of the notification of a device state.
+     */
+    @VisibleForTesting
+    static class NotificationInfo {
+        public final String name;
+        public final String activeNotificationTitle;
+        public final String activeNotificationContent;
+        public final String thermalCriticalNotificationTitle;
+        public final String thermalCriticalNotificationContent;
+
+        NotificationInfo(String name, String activeNotificationTitle,
+                String activeNotificationContent, String thermalCriticalNotificationTitle,
+                String thermalCriticalNotificationContent) {
+
+            this.name = name;
+            this.activeNotificationTitle = activeNotificationTitle;
+            this.activeNotificationContent = activeNotificationContent;
+            this.thermalCriticalNotificationTitle = thermalCriticalNotificationTitle;
+            this.thermalCriticalNotificationContent = thermalCriticalNotificationContent;
+        }
+
+        boolean hasActiveNotification() {
+            return activeNotificationTitle != null && activeNotificationTitle.length() > 0;
+        }
+
+        boolean hasThermalCriticalNotification() {
+            return thermalCriticalNotificationTitle != null
+                    && thermalCriticalNotificationTitle.length() > 0;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 109bf63..fecc13f 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -19,8 +19,12 @@
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Responsible for providing the set of supported {@link DeviceState device states} as well as the
  * current device state.
@@ -28,12 +32,42 @@
  * @see DeviceStatePolicy
  */
 public interface DeviceStateProvider {
+    int SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT = 0;
+
+    /**
+     * Indicating that the supported device states changed callback is trigger for initial listener
+     * registration.
+     */
+    int SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED = 1;
+
+    /**
+     * Indicating that the supported device states have changed because the thermal condition
+     * returned to normal status from critical status.
+     */
+    int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL = 2;
+
+    /**
+     * Indicating that the supported device states have changed because of thermal critical
+     * condition.
+     */
+    int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL = 3;
+
+    @IntDef(prefix = { "SUPPORTED_DEVICE_STATES_CHANGED_" }, value = {
+            SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT,
+            SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED,
+            SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL,
+            SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface SupportedStatesUpdatedReason {}
+
     /**
      * Registers a listener for changes in provider state.
      * <p>
-     * It is <b>required</b> that {@link Listener#onSupportedDeviceStatesChanged(DeviceState[])} be
-     * called followed by {@link Listener#onStateChanged(int)} with the initial values on successful
-     * registration of the listener.
+     * It is <b>required</b> that
+     * {@link Listener#onSupportedDeviceStatesChanged(DeviceState[], int)} be called followed by
+     * {@link Listener#onStateChanged(int)} with the initial values on successful registration of
+     * the listener.
      */
     void setListener(Listener listener);
 
@@ -53,18 +87,20 @@
          * to zero and there must always be at least one supported device state.
          *
          * @param newDeviceStates array of supported device states.
+         * @param reason the reason for the supported device states change.
          *
          * @throws IllegalArgumentException if the list of device states is empty or if one of the
          * provided states contains an invalid identifier.
          */
-        void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates);
+        void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates,
+                @SupportedStatesUpdatedReason int reason);
 
         /**
          * Called to notify the listener of a change in current device state. Required to be called
          * once on successful registration of the listener and then once on every subsequent change
          * in device state. Value must have been included in the set of supported device states
          * provided in the most recent call to
-         * {@link #onSupportedDeviceStatesChanged(DeviceState[])}.
+         * {@link #onSupportedDeviceStatesChanged(DeviceState[], int)}.
          *
          * @param identifier the identifier of the new device state.
          *
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 325e384..74cf184 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -31,6 +31,7 @@
 final class OverrideRequest {
     private final IBinder mToken;
     private final int mPid;
+    private final int mUid;
     private final int mRequestedState;
     @DeviceStateRequest.RequestFlags
     private final int mFlags;
@@ -68,10 +69,11 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface OverrideRequestType {}
 
-    OverrideRequest(IBinder token, int pid, int requestedState,
+    OverrideRequest(IBinder token, int pid, int uid, int requestedState,
             @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) {
         mToken = token;
         mPid = pid;
+        mUid = uid;
         mRequestedState = requestedState;
         mFlags = flags;
         mRequestType = requestType;
@@ -85,6 +87,10 @@
         return mPid;
     }
 
+    int getUid() {
+        return mUid;
+    }
+
     int getRequestedState() {
         return mRequestedState;
     }
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index e39c732..2ed4765 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -59,6 +59,11 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface RequestStatus {}
 
+    /**
+     * A flag indicating that the status change was triggered by thermal critical status.
+     */
+    static final int FLAG_THERMAL_CRITICAL = 1 << 0;
+
     static String statusToString(@RequestStatus int status) {
         switch (status) {
             case STATUS_ACTIVE:
@@ -106,7 +111,7 @@
     void addRequest(@NonNull OverrideRequest request) {
         OverrideRequest previousRequest = mRequest;
         mRequest = request;
-        mListener.onStatusChanged(request, STATUS_ACTIVE);
+        mListener.onStatusChanged(request, STATUS_ACTIVE, 0 /* flags */);
 
         if (previousRequest != null) {
             cancelRequestLocked(previousRequest);
@@ -116,7 +121,7 @@
     void addBaseStateRequest(@NonNull OverrideRequest request) {
         OverrideRequest previousRequest = mBaseStateRequest;
         mBaseStateRequest = request;
-        mListener.onStatusChanged(request, STATUS_ACTIVE);
+        mListener.onStatusChanged(request, STATUS_ACTIVE, 0 /* flags */);
 
         if (previousRequest != null) {
             cancelRequestLocked(previousRequest);
@@ -219,14 +224,17 @@
      * Notifies the controller that the set of supported states has changed. The controller will
      * notify the listener of all changes to request status as a result of this change.
      */
-    void handleNewSupportedStates(int[] newSupportedStates) {
+    void handleNewSupportedStates(int[] newSupportedStates,
+            @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
+        boolean isThermalCritical =
+                reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
         if (mBaseStateRequest != null && !contains(newSupportedStates,
                 mBaseStateRequest.getRequestedState())) {
-            cancelCurrentBaseStateRequestLocked();
+            cancelCurrentBaseStateRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
         }
 
         if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) {
-            cancelCurrentRequestLocked();
+            cancelCurrentRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
         }
     }
 
@@ -244,7 +252,11 @@
     }
 
     private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel) {
-        mListener.onStatusChanged(requestToCancel, STATUS_CANCELED);
+        cancelRequestLocked(requestToCancel, 0 /* flags */);
+    }
+
+    private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel, int flags) {
+        mListener.onStatusChanged(requestToCancel, STATUS_CANCELED, flags);
     }
 
     /**
@@ -252,12 +264,16 @@
      * Notifies the listener of the canceled status as well.
      */
     private void cancelCurrentRequestLocked() {
+        cancelCurrentRequestLocked(0 /* flags */);
+    }
+
+    private void cancelCurrentRequestLocked(int flags) {
         if (mRequest == null) {
             Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
             return;
         }
         mStickyRequest = false;
-        cancelRequestLocked(mRequest);
+        cancelRequestLocked(mRequest, flags);
         mRequest = null;
     }
 
@@ -266,11 +282,15 @@
      * Notifies the listener of the canceled status as well.
      */
     private void cancelCurrentBaseStateRequestLocked() {
+        cancelCurrentBaseStateRequestLocked(0 /* flags */);
+    }
+
+    private void cancelCurrentBaseStateRequestLocked(int flags) {
         if (mBaseStateRequest == null) {
             Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
             return;
         }
-        cancelRequestLocked(mBaseStateRequest);
+        cancelRequestLocked(mBaseStateRequest, flags);
         mBaseStateRequest = null;
     }
 
@@ -284,12 +304,14 @@
     }
 
     public interface StatusChangeListener {
+
         /**
          * Notifies the listener of a change in request status. If a change within the controller
          * causes one request to become active and one to become either suspended or cancelled, this
          * method is guaranteed to be called with the active request first before the suspended or
          * cancelled request.
          */
-        void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus);
+        void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus,
+                int flags);
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index fa9a100..f229d0f 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -28,7 +28,8 @@
  * Calls into SurfaceFlinger for Display creation and deletion.
  */
 public class DisplayControl {
-    private static native IBinder nativeCreateDisplay(String name, boolean secure);
+    private static native IBinder nativeCreateDisplay(String name, boolean secure,
+            float requestedRefreshRate);
     private static native void nativeDestroyDisplay(IBinder displayToken);
     private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
     private static native long[] nativeGetPhysicalDisplayIds();
@@ -46,7 +47,25 @@
      */
     public static IBinder createDisplay(String name, boolean secure) {
         Objects.requireNonNull(name, "name must not be null");
-        return nativeCreateDisplay(name, secure);
+        return nativeCreateDisplay(name, secure, 0.0f);
+    }
+
+    /**
+     * Create a display in SurfaceFlinger.
+     *
+     * @param name The name of the display
+     * @param secure Whether this display is secure.
+     * @param requestedRefreshRate The requested refresh rate in frames per second.
+     * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
+     * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded
+     * up or down to a divisor of the physical display. If 0 is specified, the virtual
+     * display is refreshed at the physical display refresh rate.
+     * @return The token reference for the display in SurfaceFlinger.
+     */
+    public static IBinder createDisplay(String name, boolean secure,
+            float requestedRefreshRate) {
+        Objects.requireNonNull(name, "name must not be null");
+        return nativeCreateDisplay(name, secure, requestedRefreshRate);
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 52e20d6..72c9a4a 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -373,9 +373,13 @@
         }
     }
 
-    private void disableRenderRateSwitching(VoteSummary summary) {
+    private void disableRenderRateSwitching(VoteSummary summary, float fps) {
         summary.minRenderFrameRate = summary.maxRenderFrameRate;
 
+        if (!isRenderRateAchievable(fps, summary)) {
+            summary.minRenderFrameRate = summary.maxRenderFrameRate = fps;
+        }
+
         if (mLoggingEnabled) {
             Slog.i(TAG, "Disabled render rate switching on summary: " + summary);
         }
@@ -540,10 +544,10 @@
                 disableModeSwitching(primarySummary, fps);
                 if (modeSwitchingDisabled) {
                     disableModeSwitching(appRequestSummary, fps);
-                    disableRenderRateSwitching(primarySummary);
+                    disableRenderRateSwitching(primarySummary, fps);
 
                     if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) {
-                        disableRenderRateSwitching(appRequestSummary);
+                        disableRenderRateSwitching(appRequestSummary, fps);
                     }
                 }
             }
@@ -570,6 +574,22 @@
         }
     }
 
+    private boolean isRenderRateAchievable(float physicalRefreshRate, VoteSummary summary) {
+        // Check whether the render frame rate range is achievable by the mode's physical
+        // refresh rate, meaning that if a divisor of the physical refresh rate is in range
+        // of the render frame rate.
+        // For example for the render frame rate [50, 70]:
+        //   - 120Hz is in range as we can render at 60hz by skipping every other frame,
+        //     which is within the render rate range
+        //   - 90hz is not in range as none of the even divisors (i.e. 90, 45, 30)
+        //     fall within the acceptable render range.
+        final int divisor =
+                (int) Math.ceil((physicalRefreshRate / summary.maxRenderFrameRate)
+                        - FLOAT_TOLERANCE);
+        float adjustedPhysicalRefreshRate = physicalRefreshRate / divisor;
+        return adjustedPhysicalRefreshRate >= (summary.minRenderFrameRate - FLOAT_TOLERANCE);
+    }
+
     private ArrayList<Display.Mode> filterModes(Display.Mode[] supportedModes,
             VoteSummary summary) {
         if (summary.minRenderFrameRate > summary.maxRenderFrameRate + FLOAT_TOLERANCE) {
@@ -627,22 +647,9 @@
                 }
             }
 
-            // Check whether the render frame rate range is achievable by the mode's physical
-            // refresh rate, meaning that if a divisor of the physical refresh rate is in range
-            // of the render frame rate.
-            // For example for the render frame rate [50, 70]:
-            //   - 120Hz is in range as we can render at 60hz by skipping every other frame,
-            //     which is within the render rate range
-            //   - 90hz is not in range as none of the even divisors (i.e. 90, 45, 30)
-            //     fall within the acceptable render range.
-            final int divisor =
-                    (int) Math.ceil((physicalRefreshRate / summary.maxRenderFrameRate)
-                            - FLOAT_TOLERANCE);
-            float adjustedPhysicalRefreshRate = physicalRefreshRate / divisor;
-            if (adjustedPhysicalRefreshRate < (summary.minRenderFrameRate - FLOAT_TOLERANCE)) {
+            if (!isRenderRateAchievable(physicalRefreshRate, summary)) {
                 if (mLoggingEnabled) {
                     Slog.w(TAG, "Discarding mode " + mode.getModeId()
-                            + " with adjusted refresh rate: " + adjustedPhysicalRefreshRate
                             + ", outside frame rate bounds"
                             + ": minRenderFrameRate=" + summary.minRenderFrameRate
                             + ", maxRenderFrameRate=" + summary.maxRenderFrameRate
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index ddeaa1b..364d53b 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -106,7 +106,8 @@
         String name = virtualDisplayConfig.getName();
         boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
         IBinder appToken = callback.asBinder();
-        IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure);
+        IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure,
+                virtualDisplayConfig.getRequestedRefreshRate());
         final String baseUniqueId =
                 UNIQUE_ID_PREFIX + ownerPackageName + "," + ownerUid + "," + name + ",";
         final int uniqueIndex = getNextUniqueIndex(baseUniqueId);
@@ -247,6 +248,7 @@
         private int mWidth;
         private int mHeight;
         private int mDensityDpi;
+        private float mRequestedRefreshRate;
         private Surface mSurface;
         private DisplayDeviceInfo mInfo;
         private int mDisplayState;
@@ -270,8 +272,9 @@
             mName = virtualDisplayConfig.getName();
             mWidth = virtualDisplayConfig.getWidth();
             mHeight = virtualDisplayConfig.getHeight();
-            mMode = createMode(mWidth, mHeight, REFRESH_RATE);
             mDensityDpi = virtualDisplayConfig.getDensityDpi();
+            mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate();
+            mMode = createMode(mWidth, mHeight, getRefreshRate());
             mSurface = surface;
             mFlags = flags;
             mCallback = callback;
@@ -410,7 +413,7 @@
                 sendTraversalRequestLocked();
                 mWidth = width;
                 mHeight = height;
-                mMode = createMode(width, height, REFRESH_RATE);
+                mMode = createMode(width, height, getRefreshRate());
                 mDensityDpi = densityDpi;
                 mInfo = null;
                 mPendingChanges |= PENDING_RESIZE;
@@ -438,9 +441,9 @@
             pw.println("mStopped=" + mStopped);
             pw.println("mDisplayIdToMirror=" + mDisplayIdToMirror);
             pw.println("mWindowManagerMirroring=" + mIsWindowManagerMirroring);
+            pw.println("mRequestedRefreshRate=" + mRequestedRefreshRate);
         }
 
-
         @Override
         public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
             if (mInfo == null) {
@@ -456,7 +459,7 @@
                 mInfo.densityDpi = mDensityDpi;
                 mInfo.xDpi = mDensityDpi;
                 mInfo.yDpi = mDensityDpi;
-                mInfo.presentationDeadlineNanos = 1000000000L / (int) REFRESH_RATE; // 1 frame
+                mInfo.presentationDeadlineNanos = 1000000000L / (int) getRefreshRate(); // 1 frame
                 mInfo.flags = 0;
                 if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
@@ -554,6 +557,10 @@
             }
             return mInfo;
         }
+
+        private float getRefreshRate() {
+            return (mRequestedRefreshRate != 0.0f) ? mRequestedRefreshRate : REFRESH_RATE;
+        }
     }
 
     private static class Callback extends Handler {
@@ -632,6 +639,19 @@
 
     @VisibleForTesting
     public interface SurfaceControlDisplayFactory {
-        public IBinder createDisplay(String name, boolean secure);
+        /**
+         * Create a virtual display in SurfaceFlinger.
+         *
+         * @param name The name of the display
+         * @param secure Whether this display is secure.
+         * @param requestedRefreshRate
+         *     The refresh rate, frames per second, to request on the virtual display.
+         *     It should be a divisor of refresh rate of the leader physical display
+         *     that drives VSYNC, e.g. 30/60fps on 120fps display. If an arbitrary refresh
+         *     rate is specified, SurfaceFlinger rounds up or down to match a divisor of
+         *     the refresh rate of the leader physical display.
+         * @return The token reference for the display in SurfaceFlinger.
+         */
+        IBinder createDisplay(String name, boolean secure, float requestedRefreshRate);
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index e21895a..298f6ad 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -27,7 +27,6 @@
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT;
 
 import android.annotation.Nullable;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.util.EventLog;
@@ -65,13 +64,10 @@
 
     @GuardedBy("ImfLock.class")
     @Override
-    public void performShowIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+    public void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
             int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
         if (curMethod != null) {
-            // create a placeholder token for IMS so that IMS cannot inject windows into client app.
-            final IBinder showInputToken = new Binder();
-            mService.setRequestImeTokenToWindow(windowToken, showInputToken);
             if (DEBUG) {
                 Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
                         + ", " + showFlags + ", " + resultReceiver + ") for reason: "
@@ -86,7 +82,7 @@
                             InputMethodDebug.softInputModeToString(
                                     mService.mCurFocusedWindowSoftInputMode));
                 }
-                mService.onShowHideSoftInputRequested(true /* show */, windowToken, reason,
+                mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
                         statsToken);
             }
         }
@@ -94,12 +90,10 @@
 
     @GuardedBy("ImfLock.class")
     @Override
-    public void performHideIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+    public void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
         if (curMethod != null) {
-            final Binder hideInputToken = new Binder();
-            mService.setRequestImeTokenToWindow(windowToken, hideInputToken);
             // The IME will report its visible state again after the following message finally
             // delivered to the IME process as an IPC.  Hence the inconsistency between
             // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
@@ -118,7 +112,7 @@
                             InputMethodDebug.softInputModeToString(
                                     mService.mCurFocusedWindowSoftInputMode));
                 }
-                mService.onShowHideSoftInputRequested(false /* show */, windowToken, reason,
+                mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
                         statsToken);
             }
         }
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
index e97ec93..f03e985 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
@@ -31,26 +31,26 @@
     /**
      * Performs showing IME on top of the given window.
      *
-     * @param windowToken    The token of a window that currently has focus.
+     * @param showInputToken A token that represents the requester to show IME.
      * @param statsToken     A token that tracks the progress of an IME request.
      * @param showFlags      Provides additional operating flags to show IME.
      * @param resultReceiver If non-null, this will be called back to the caller when
      *                       it has processed request to tell what it has done.
      * @param reason         The reason for requesting to show IME.
      */
-    default void performShowIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+    default void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
             int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {}
 
     /**
      * Performs hiding IME to the given window
      *
-     * @param windowToken    The token of a window that currently has focus.
+     * @param hideInputToken A token that represents the requester to hide IME.
      * @param statsToken     A token that tracks the progress of an IME request.
      * @param resultReceiver If non-null, this will be called back to the caller when
      *                       it has processed request to tell what it has done.
      * @param reason         The reason for requesting to hide IME.
      */
-    default void performHideIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+    default void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {}
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index db61e954..eaca842 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -35,6 +35,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.content.res.Configuration;
+import android.os.Binder;
 import android.os.IBinder;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
@@ -263,6 +264,8 @@
             // policy.
             mPolicy.mPendingA11yRequestingHideKeyboard = false;
         }
+        // create a placeholder token for IMS so that IMS cannot inject windows into client app.
+        state.setRequestImeToken(new Binder());
         setWindowStateInner(windowToken, state);
     }
 
@@ -279,14 +282,6 @@
         return state;
     }
 
-    void setRequestImeTokenToWindow(IBinder windowToken, IBinder token) {
-        ImeTargetWindowState state = getWindowStateOrNull(windowToken);
-        if (state != null) {
-            state.setRequestImeToken(token);
-            setWindowStateInner(windowToken, state);
-        }
-    }
-
     void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
         final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
         if (state != null && newState.hasEdiorFocused()) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2ced988..27daceb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3344,11 +3344,6 @@
         }
     }
 
-    @GuardedBy("ImfLock.class")
-    void setRequestImeTokenToWindow(IBinder windowToken, IBinder token) {
-        mVisibilityStateComputer.setRequestImeTokenToWindow(windowToken, token);
-    }
-
     @BinderThread
     @Override
     public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
@@ -4603,10 +4598,13 @@
         }
     }
 
-    /** Called right after {@link com.android.internal.inputmethod.IInputMethod#showSoftInput}. */
+    /**
+     * Called right after {@link IInputMethod#showSoftInput} or {@link IInputMethod#hideSoftInput}.
+     */
     @GuardedBy("ImfLock.class")
-    void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
+    void onShowHideSoftInputRequested(boolean show, IBinder requestImeToken,
             @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken) {
+        final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
         final WindowManagerInternal.ImeTargetInfo info =
                 mWindowManagerInternal.onToggleImeRequested(
                         show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index ffc309e..3c97aaf8 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -280,13 +280,13 @@
     public void setRouteListingPreference(
             @NonNull IMediaRouter2 router,
             @Nullable RouteListingPreference routeListingPreference) {
-        ComponentName inAppOnlyItemRoutingReceiver =
+        ComponentName linkedItemLandingComponent =
                 routeListingPreference != null
-                        ? routeListingPreference.getInAppOnlyItemRoutingReceiver()
+                        ? routeListingPreference.getLinkedItemComponentName()
                         : null;
-        if (inAppOnlyItemRoutingReceiver != null) {
+        if (linkedItemLandingComponent != null) {
             MediaServerUtils.enforcePackageName(
-                    inAppOnlyItemRoutingReceiver.getPackageName(), Binder.getCallingUid());
+                    linkedItemLandingComponent.getPackageName(), Binder.getCallingUid());
         }
 
         final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 69436da..8d40adf 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -27,12 +27,15 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
+import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
@@ -51,6 +54,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Set;
@@ -122,7 +126,18 @@
         @Override
         public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
                 @PackageManager.PackageInfoFlagsBits long flags, int userId) {
-            return mService.getBackgroundInstalledPackages(flags, userId);
+            if (!Build.IS_DEBUGGABLE) {
+                return mService.getBackgroundInstalledPackages(flags, userId);
+            }
+            // The debug.transparency.bg-install-apps (only works for debuggable builds)
+            // is used to set mock list of background installed apps for testing.
+            // The list of apps' names is delimited by ",".
+            String propertyString = SystemProperties.get("debug.transparency.bg-install-apps");
+            if (TextUtils.isEmpty(propertyString)) {
+                return mService.getBackgroundInstalledPackages(flags, userId);
+            } else {
+                return mService.getMockBackgroundInstalledPackages(propertyString);
+            }
         }
     }
 
@@ -145,6 +160,27 @@
         return new ParceledListSlice<>(packages);
     }
 
+    /**
+     * Mock a list of background installed packages based on the property string.
+     */
+    @NonNull
+    ParceledListSlice<PackageInfo> getMockBackgroundInstalledPackages(
+            @NonNull String propertyString) {
+        String[] mockPackageNames = propertyString.split(",");
+        List<PackageInfo> mockPackages = new ArrayList<>();
+        for (String name : mockPackageNames) {
+            try {
+                PackageInfo packageInfo = mPackageManager.getPackageInfo(name,
+                        PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
+                mockPackages.add(packageInfo);
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.w(TAG, "Package's PackageInfo not found " + name);
+                continue;
+            }
+        }
+        return new ParceledListSlice<>(mockPackages);
+    }
+
     private static class EventHandler extends Handler {
         private final BackgroundInstallControlService mService;
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4808d4c..0d5392b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -70,6 +70,7 @@
 import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_STOPPED_SYSTEM_APP;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
@@ -1171,7 +1172,8 @@
         request.setName(pkgName);
         if (parsedPackage.isTestOnly()) {
             if ((installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) {
-                throw new PrepareFailure(INSTALL_FAILED_TEST_ONLY, "installPackageLI");
+                throw new PrepareFailure(INSTALL_FAILED_TEST_ONLY,
+                        "Failed to install test-only apk. Did you forget to add -t?");
             }
         }
 
@@ -4236,6 +4238,18 @@
             }
         }
 
+        // A new application appeared on /system, and we are seeing it for the first time.
+        // Its also not updated as we don't have a copy of it on /data. So, scan it in a
+        // STOPPED state. Ignore if it's an APEX package since stopped state does not affect them.
+        final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0;
+        if (mPm.mShouldStopSystemPackagesByDefault && scanSystemPartition
+                && !pkgAlreadyExists && !isApexPkg) {
+            String packageName = parsedPackage.getPackageName();
+            if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)) {
+                scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP;
+            }
+        }
+
         final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
                 scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
         return new Pair<>(scanResult, shouldHideSystemApp);
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 7b759e3..43c0e05 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -525,13 +525,13 @@
     }
 
     private void processApkInstallRequests(boolean success, List<InstallRequest> installRequests) {
-        if (success) {
+        if (!success) {
             for (InstallRequest request : installRequests) {
                 if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
                     cleanUpForFailedInstall(request);
                 }
             }
-
+        } else {
             mInstallPackageHelper.installPackagesTraced(installRequests);
 
             for (InstallRequest request : installRequests) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 239853c..d23ea16 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -184,6 +184,7 @@
     private volatile boolean mOkToSendBroadcasts = false;
     private volatile boolean mBypassNextStagedInstallerCheck = false;
     private volatile boolean mBypassNextAllowedApexUpdateCheck = false;
+    private volatile int mDisableVerificationForUid = INVALID_UID;
 
     /**
      * File storing persisted {@link #mSessions} metadata.
@@ -705,14 +706,23 @@
             }
         }
 
-        if (Build.IS_DEBUGGABLE || isCalledBySystemOrShell(callingUid)) {
+        if (Build.IS_DEBUGGABLE || isCalledBySystem(callingUid)) {
             params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
         } else {
             params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
             params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE;
         }
 
-        if ((params.installFlags & ADB_DEV_MODE) != ADB_DEV_MODE) {
+        if (mDisableVerificationForUid != INVALID_UID) {
+            if (callingUid == mDisableVerificationForUid) {
+                params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
+            } else {
+                // Clear the flag if current calling uid doesn't match the requested uid.
+                params.installFlags &= ~PackageManager.INSTALL_DISABLE_VERIFICATION;
+            }
+            // Reset the field as this is a one-off request.
+            mDisableVerificationForUid = INVALID_UID;
+        } else if ((params.installFlags & ADB_DEV_MODE) != ADB_DEV_MODE) {
             // Only tools under specific conditions (test app installed through ADB, and
             // verification disabled flag specified) can disable verification.
             params.installFlags &= ~PackageManager.INSTALL_DISABLE_VERIFICATION;
@@ -906,6 +916,10 @@
         return sessionId;
     }
 
+    private static boolean isCalledBySystem(int callingUid) {
+        return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID;
+    }
+
     private boolean isCalledBySystemOrShell(int callingUid) {
         return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID
                 || callingUid == Process.SHELL_UID;
@@ -1367,6 +1381,14 @@
         mBypassNextAllowedApexUpdateCheck = value;
     }
 
+    @Override
+    public void disableVerificationForUid(int uid) {
+        if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+            throw new SecurityException("Operation not allowed for caller");
+        }
+        mDisableVerificationForUid = uid;
+    }
+
     /**
      * Set an installer to allow for the unlimited silent updates.
      */
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a02a419..c6a1579 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -387,6 +387,7 @@
     static final int SCAN_DROP_CACHE = 1 << 24;
     static final int SCAN_AS_FACTORY = 1 << 25;
     static final int SCAN_AS_APEX = 1 << 26;
+    static final int SCAN_AS_STOPPED_SYSTEM_APP = 1 << 27;
 
     @IntDef(flag = true, prefix = { "SCAN_" }, value = {
             SCAN_NO_DEX,
@@ -403,6 +404,7 @@
             SCAN_AS_INSTANT_APP,
             SCAN_AS_FULL_APP,
             SCAN_AS_VIRTUAL_PRELOAD,
+            SCAN_AS_STOPPED_SYSTEM_APP,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ScanFlags {}
@@ -964,6 +966,8 @@
     final @Nullable String mRecentsPackage;
     final @Nullable String mAmbientContextDetectionPackage;
     final @Nullable String mWearableSensingPackage;
+    final @NonNull Set<String> mInitialNonStoppedSystemPackages;
+    final boolean mShouldStopSystemPackagesByDefault;
     private final @NonNull String mRequiredSdkSandboxPackage;
 
     @GuardedBy("mLock")
@@ -1771,6 +1775,8 @@
         mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
         mResolveComponentName = testParams.resolveComponentName;
         mRequiredSdkSandboxPackage = testParams.requiredSdkSandboxPackage;
+        mInitialNonStoppedSystemPackages = testParams.initialNonStoppedSystemPackages;
+        mShouldStopSystemPackagesByDefault = testParams.shouldStopSystemPackagesByDefault;
 
         mLiveComputer = createLiveComputer();
         mSnapshotStatistics = null;
@@ -2097,6 +2103,11 @@
             mCacheDir = PackageManagerServiceUtils.preparePackageParserCache(
                     mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
 
+            mInitialNonStoppedSystemPackages = mInjector.getSystemConfig()
+                    .getInitialNonStoppedSystemPackages();
+            mShouldStopSystemPackagesByDefault = mContext.getResources()
+                    .getBoolean(R.bool.config_stopSystemPackagesByDefault);
+
             final int[] userIds = mUserManager.getUserIds();
             PackageParser2 packageParser = mInjector.getScanningCachingPackageParser();
             mOverlayConfig = mInitAppsHelper.initSystemApps(packageParser, packageSettings, userIds,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index e5cfa67..0ed90e4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.incremental.IncrementalManager;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.DisplayMetrics;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -40,6 +41,7 @@
 
 import java.io.File;
 import java.util.List;
+import java.util.Set;
 
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
 public final class PackageManagerServiceTestParams {
@@ -119,4 +121,6 @@
     public SuspendPackageHelper suspendPackageHelper;
     public DistractingPackageHelper distractingPackageHelper;
     public StorageEventHelper storageEventHelper;
+    public Set<String> initialNonStoppedSystemPackages = new ArraySet<>();
+    public boolean shouldStopSystemPackagesByDefault;
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 4263791..8beb0b6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -344,6 +344,8 @@
                     return runBypassStagedInstallerCheck();
                 case "bypass-allowed-apex-update-check":
                     return runBypassAllowedApexUpdateCheck();
+                case "disable-verification-for-uid":
+                    return runDisableVerificationForUid();
                 case "set-silent-updates-policy":
                     return runSetSilentUpdatesPolicy();
                 default: {
@@ -516,6 +518,29 @@
         }
     }
 
+    private int runDisableVerificationForUid() {
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            int uid = Integer.parseInt(getNextArgRequired());
+            var amInternal = LocalServices.getService(ActivityManagerInternal.class);
+            boolean isInstrumented =
+                    amInternal.getInstrumentationSourceUid(uid) != Process.INVALID_UID;
+            if (isInstrumented) {
+                mInterface.getPackageInstaller().disableVerificationForUid(uid);
+                return 0;
+            } else {
+                // Only available for testing
+                pw.println("Error: must specify an instrumented uid");
+                return -1;
+            }
+        } catch (RemoteException e) {
+            pw.println("Failure ["
+                    + e.getClass().getName() + " - "
+                    + e.getMessage() + "]");
+            return -1;
+        }
+    }
+
     private int uninstallSystemUpdates(String packageName) {
         final PrintWriter pw = getOutPrintWriter();
         boolean failedUninstalls = false;
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 830b096c..2538871 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -31,6 +31,7 @@
 import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_STOPPED_SYSTEM_APP;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
@@ -198,6 +199,7 @@
         if (createNewPackage) {
             final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
             final boolean virtualPreload = (scanFlags & SCAN_AS_VIRTUAL_PRELOAD) != 0;
+            final boolean isStoppedSystemApp = (scanFlags & SCAN_AS_STOPPED_SYSTEM_APP) != 0;
 
             // Flags contain system values stored in the server variant of AndroidPackage,
             // and so the server-side PackageInfoUtils is still called, even without a
@@ -212,7 +214,7 @@
                     AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage),
                     AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
                     parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
-                    true /*allowInstall*/, instantApp, virtualPreload,
+                    true /*allowInstall*/, instantApp, virtualPreload, isStoppedSystemApp,
                     UserManagerService.getInstance(), usesSdkLibraries,
                     parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
                     parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 165e476..4f0a115 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1041,7 +1041,7 @@
             File codePath, String legacyNativeLibraryPath, String primaryCpuAbi,
             String secondaryCpuAbi, long versionCode, int pkgFlags, int pkgPrivateFlags,
             UserHandle installUser, boolean allowInstall, boolean instantApp,
-            boolean virtualPreload, UserManagerService userManager,
+            boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
             String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
             String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
             Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
@@ -1068,6 +1068,9 @@
             pkgSetting.setFlags(pkgFlags)
                     .setPrivateFlags(pkgPrivateFlags);
         } else {
+            int installUserId = installUser != null ? installUser.getIdentifier()
+                    : UserHandle.USER_SYSTEM;
+
             pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
                     legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
                     null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
@@ -1086,8 +1089,6 @@
                     Slog.i(PackageManagerService.TAG, "Stopping package " + pkgName, e);
                 }
                 List<UserInfo> users = getAllUsers(userManager);
-                int installUserId = installUser != null ? installUser.getIdentifier()
-                        : UserHandle.USER_SYSTEM;
                 if (users != null && allowInstall) {
                     for (UserInfo user : users) {
                         // By default we consider this app to be installed
@@ -1126,6 +1127,13 @@
                         );
                     }
                 }
+            } else if (isStoppedSystemApp) {
+                if (DEBUG_STOPPED) {
+                    RuntimeException e = new RuntimeException("here");
+                    e.fillInStackTrace();
+                    Slog.i(PackageManagerService.TAG, "Stopping system package " + pkgName, e);
+                }
+                pkgSetting.setStopped(true, installUserId);
             }
             if (sharedUser != null) {
                 pkgSetting.setAppId(sharedUser.mAppId);
@@ -4422,6 +4430,15 @@
                                 ps.getPackageName()));
                 // Only system apps are initially installed.
                 ps.setInstalled(shouldReallyInstall, userHandle);
+
+                // Non-Apex system apps, that are not included in the allowlist in
+                // initialNonStoppedSystemPackages, should be marked as stopped by default.
+                final boolean shouldBeStopped = service.mShouldStopSystemPackagesByDefault
+                        && ps.isSystem()
+                        && !ps.isApex()
+                        && !service.mInitialNonStoppedSystemPackages.contains(ps.getPackageName());
+                ps.setStopped(shouldBeStopped, userHandle);
+
                 // If userTypeInstallablePackages is the *only* reason why we're not installing,
                 // then uninstallReason is USER_TYPE. If there's a different reason, or if we
                 // actually are installing, put UNKNOWN.
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 3c5f309..26a990c 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -52,22 +52,23 @@
     // TODO(b/248408342): Move keep annotation to the method referencing these fields reflectively.
     @Keep public static final int USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE = 1;
     @Keep public static final int USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE = 2;
+    @Keep public static final int USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE = 3;
     @Keep public static final int USER_ASSIGNMENT_RESULT_FAILURE = -1;
 
     private static final String PREFIX_USER_ASSIGNMENT_RESULT = "USER_ASSIGNMENT_RESULT_";
     @IntDef(flag = false, prefix = {PREFIX_USER_ASSIGNMENT_RESULT}, value = {
             USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE,
             USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE,
+            USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE,
             USER_ASSIGNMENT_RESULT_FAILURE
     })
     public @interface UserAssignmentResult {}
 
-    // TODO(b/248408342): Move keep annotation to the method referencing these fields reflectively.
-    @Keep public static final int USER_START_MODE_FOREGROUND = 1;
-    @Keep public static final int USER_START_MODE_BACKGROUND = 2;
-    @Keep public static final int USER_START_MODE_BACKGROUND_VISIBLE = 3;
-
     private static final String PREFIX_USER_START_MODE = "USER_START_MODE_";
+
+    /**
+     * Type used to indicate how a user started.
+     */
     @IntDef(flag = false, prefix = {PREFIX_USER_START_MODE}, value = {
             USER_START_MODE_FOREGROUND,
             USER_START_MODE_BACKGROUND,
@@ -75,6 +76,32 @@
     })
     public @interface UserStartMode {}
 
+    // TODO(b/248408342): Move keep annotations below to the method referencing these fields
+    // reflectively.
+
+    /** (Full) user started on foreground (a.k.a. "current user"). */
+    @Keep public static final int USER_START_MODE_FOREGROUND = 1;
+
+    /**
+     * User (full or profile) started on background and is
+     * {@link UserManager#isUserVisible() invisible}.
+     *
+     * <p>This is the "traditional" way of starting a background user, and can be used to start
+     * profiles as well, although starting an invisible profile is not common from the System UI
+     * (it could be done through APIs or adb, though).
+     */
+    @Keep public static final int USER_START_MODE_BACKGROUND = 2;
+
+    /**
+     * User (full or profile) started on background and is
+     * {@link UserManager#isUserVisible() visible}.
+     *
+     * <p>This is the "traditional" way of starting a profile (i.e., when the profile of the current
+     * user is the current foreground user), but it can also be used to start a full user associated
+     * with a display (which is the case on automotives with passenger displays).
+     */
+    @Keep public static final int USER_START_MODE_BACKGROUND_VISIBLE = 3;
+
     public interface UserRestrictionsListener {
         /**
          * Called when a user restriction changes.
diff --git a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
index 5bdcbb9..50a1d90 100644
--- a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
+++ b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Process;
@@ -36,6 +37,7 @@
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.os.RoSystemProperties;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
@@ -103,6 +105,9 @@
         pw.println();
         pw.println("  is-headless-system-user-mode [-v | --verbose]");
         pw.println("    Checks whether the device uses headless system user mode.");
+        pw.println("  is-visible-background-users-on-default-display-supported [-v | --verbose]");
+        pw.println("    Checks whether the device allows users to be start visible on background "
+                + "in the default display.");
         pw.println("    It returns the effective mode, even when using emulation");
         pw.println("    (to get the real mode as well, use -v or --verbose)");
         pw.println();
@@ -129,6 +134,8 @@
                     return runSetSystemUserModeEmulation();
                 case "is-headless-system-user-mode":
                     return runIsHeadlessSystemUserMode();
+                case "is-visible-background-users-on-default-display-supported":
+                    return runIsVisibleBackgroundUserOnDefaultDisplaySupported();
                 case "is-user-visible":
                     return runIsUserVisible();
                 default:
@@ -431,19 +438,47 @@
                     return -1;
             }
         }
-
-        boolean isHsum = mService.isHeadlessSystemUserMode();
+        boolean effective = mService.isHeadlessSystemUserMode();
         if (!verbose) {
             // NOTE: do not change output below, as it's used by ITestDevice
             // (it's ok to change the verbose option though)
-            pw.println(isHsum);
+            pw.println(effective);
         } else {
-            pw.printf("effective=%b real=%b\n", isHsum,
+            pw.printf("effective=%b real=%b\n", effective,
                     RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER);
         }
         return 0;
     }
 
+    private int runIsVisibleBackgroundUserOnDefaultDisplaySupported() {
+        PrintWriter pw = getOutPrintWriter();
+
+        boolean verbose = false;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-v":
+                case "--verbose":
+                    verbose = true;
+                    break;
+                default:
+                    pw.println("Invalid option: " + opt);
+                    return -1;
+            }
+        }
+
+        boolean effective = UserManager.isVisibleBackgroundUsersOnDefaultDisplayEnabled();
+        if (!verbose) {
+            // NOTE: do not change output below, as it's used by ITestDevice
+            // (it's ok to change the verbose option though)
+            pw.println(effective);
+        } else {
+            pw.printf("effective=%b real=%b\n", effective, Resources.getSystem()
+                    .getBoolean(R.bool.config_multiuserVisibleBackgroundUsersOnDefaultDisplay));
+        }
+        return 0;
+    }
+
     /**
      * Gets the {@link UserManager} associated with the context of the given user.
      */
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 77c32ae..4cf8c09 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -121,6 +121,16 @@
                 .setBaseType(FLAG_PROFILE)
                 .setMaxAllowedPerParent(1)
                 .setLabel(0)
+                .setIconBadge(com.android.internal.R.drawable.ic_clone_icon_badge)
+                .setBadgePlain(com.android.internal.R.drawable.ic_clone_badge)
+                // Clone doesn't use BadgeNoBackground, so just set to BadgePlain as a placeholder.
+                .setBadgeNoBackground(com.android.internal.R.drawable.ic_clone_badge)
+                .setBadgeLabels(
+                        com.android.internal.R.string.clone_profile_label_badge)
+                .setBadgeColors(
+                        com.android.internal.R.color.system_neutral2_800)
+                .setDarkThemeBadgeColors(
+                        com.android.internal.R.color.system_neutral2_900)
                 .setDefaultRestrictions(null)
                 .setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter())
                 .setDefaultUserProperties(new UserProperties.Builder()
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 1f935f90..fe8a500 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -22,6 +22,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
@@ -41,6 +42,7 @@
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
+import android.util.Log;
 import android.util.SparseIntArray;
 import android.view.Display;
 
@@ -54,6 +56,8 @@
 import com.android.server.utils.Slogf;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -76,11 +80,11 @@
  */
 public final class UserVisibilityMediator implements Dumpable {
 
-    private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
-    private static final boolean VERBOSE = false; // DO NOT SUBMIT WITH TRUE
-
     private static final String TAG = UserVisibilityMediator.class.getSimpleName();
 
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean VERBOSE = false; // DO NOT SUBMIT WITH TRUE
+
     private static final String PREFIX_SECONDARY_DISPLAY_MAPPING = "SECONDARY_DISPLAY_MAPPING_";
     public static final int SECONDARY_DISPLAY_MAPPING_NEEDED = 1;
     public static final int SECONDARY_DISPLAY_MAPPING_NOT_NEEDED = 2;
@@ -97,7 +101,7 @@
     })
     public @interface SecondaryDisplayMappingStatus {}
 
-    // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
+    // TODO(b/266158156): might need to change this if boot logic is refactored for HSUM devices
     @VisibleForTesting
     static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM;
 
@@ -131,10 +135,23 @@
     private final SparseIntArray mExtraDisplaysAssignedToUsers;
 
     /**
-     * Mapping from each started user to its profile group.
+     * Mapping of each user that started visible (key) to its profile group id (value).
+     *
+     * <p>It's used to determine not just if the user is visible, but also
+     * {@link #isProfile(int, int) if it's a profile}.
      */
     @GuardedBy("mLock")
-    private final SparseIntArray mStartedProfileGroupIds = new SparseIntArray();
+    private final SparseIntArray mStartedVisibleProfileGroupIds = new SparseIntArray();
+
+    /**
+     * List of profiles that have explicitly started invisible.
+     *
+     * <p>Only used for debugging purposes (and set when {@link #DBG} is {@code true}), hence we
+     * don't care about autoboxing.
+     */
+    @GuardedBy("mLock")
+    @Nullable
+    private final List<Integer> mStartedInvisibleProfileUserIds;
 
     /**
      * Handler user to call listeners
@@ -147,8 +164,7 @@
 
     UserVisibilityMediator(Handler handler) {
         this(UserManager.isVisibleBackgroundUsersEnabled(),
-                // TODO(b/261538232): get visibleBackgroundUserOnDefaultDisplayAllowed from UM
-                /* visibleBackgroundUserOnDefaultDisplayAllowed= */ false, handler);
+                UserManager.isVisibleBackgroundUsersOnDefaultDisplayEnabled(), handler);
     }
 
     @VisibleForTesting
@@ -164,9 +180,14 @@
             mUsersAssignedToDisplayOnStart = null;
             mExtraDisplaysAssignedToUsers = null;
         }
+        mStartedInvisibleProfileUserIds = DBG ? new ArrayList<>(4) : null;
         mHandler = handler;
-        // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
-        mStartedProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID);
+        // TODO(b/266158156): might need to change this if boot logic is refactored for HSUM devices
+        mStartedVisibleProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID);
+
+        if (DBG) {
+            Slogf.i(TAG, "UserVisibilityMediator created with DBG on");
+        }
     }
 
     /**
@@ -177,6 +198,8 @@
             int displayId) {
         Preconditions.checkArgument(!isSpecialUserId(userId), "user id cannot be generic: %d",
                 userId);
+        validateUserStartMode(userStartMode);
+
         // This method needs to perform 4 actions:
         //
         // 1. Check if the user can be started given the provided arguments
@@ -207,7 +230,8 @@
                 Slogf.d(TAG, "result of getUserVisibilityOnStartLocked(%s)",
                         userAssignmentResultToString(result));
             }
-            if (result == USER_ASSIGNMENT_RESULT_FAILURE) {
+            if (result == USER_ASSIGNMENT_RESULT_FAILURE
+                    || result == USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE) {
                 return result;
             }
 
@@ -223,14 +247,29 @@
 
             visibleUsersBefore = getVisibleUsers();
 
-            // Set current user / profiles state
-            if (userStartMode == USER_START_MODE_FOREGROUND) {
-                mCurrentUserId = userId;
+            // Set current user / started users state
+            switch (userStartMode) {
+                case USER_START_MODE_FOREGROUND:
+                    mCurrentUserId = userId;
+                    // Fallthrough
+                case USER_START_MODE_BACKGROUND_VISIBLE:
+                    if (DBG) {
+                        Slogf.d(TAG, "adding visible user / profile group id mapping (%d -> %d)",
+                                userId, profileGroupId);
+                    }
+                    mStartedVisibleProfileGroupIds.put(userId, profileGroupId);
+                    break;
+                case USER_START_MODE_BACKGROUND:
+                    if (mStartedInvisibleProfileUserIds != null
+                            && isProfile(userId, profileGroupId)) {
+                        Slogf.d(TAG, "adding user %d to list of invisible profiles", userId);
+                        mStartedInvisibleProfileUserIds.add(userId);
+                    }
+                    break;
+                default:
+                    Slogf.wtf(TAG,  "invalid userStartMode passed to assignUserToDisplayOnStart: "
+                            + "%d", userStartMode);
             }
-            if (DBG) {
-                Slogf.d(TAG, "adding user / profile mapping (%d -> %d)", userId, profileGroupId);
-            }
-            mStartedProfileGroupIds.put(userId, profileGroupId);
 
             //  Set user / display state
             switch (mappingResult) {
@@ -278,49 +317,64 @@
         }
 
         boolean visibleBackground = userStartMode == USER_START_MODE_BACKGROUND_VISIBLE;
-        if (displayId == DEFAULT_DISPLAY && visibleBackground
-                && !mVisibleBackgroundUserOnDefaultDisplayAllowed
-                && !isProfile(userId, profileGroupId)) {
-            Slogf.wtf(TAG, "cannot start full user (%d) visible on default display", userId);
-            return USER_ASSIGNMENT_RESULT_FAILURE;
+        if (displayId == DEFAULT_DISPLAY && visibleBackground) {
+            if (mVisibleBackgroundUserOnDefaultDisplayAllowed && isCurrentUserLocked(userId)) {
+                // Shouldn't happen - UserController returns before calling this method
+                Slogf.wtf(TAG, "trying to start current user (%d) visible in background on default"
+                        + " display", userId);
+                return USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
+
+            }
+            if (!mVisibleBackgroundUserOnDefaultDisplayAllowed
+                    && !isProfile(userId, profileGroupId)) {
+                Slogf.wtf(TAG, "cannot start full user (%d) visible on default display", userId);
+                return USER_ASSIGNMENT_RESULT_FAILURE;
+            }
         }
 
         boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
         if (displayId != DEFAULT_DISPLAY) {
             if (foreground) {
-                Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: cannot start "
+                Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: cannot start "
                         + "foreground user on secondary display", userId, profileGroupId,
-                        foreground, displayId);
+                        userStartModeToString(userStartMode), displayId);
                 return USER_ASSIGNMENT_RESULT_FAILURE;
             }
             if (!mVisibleBackgroundUsersEnabled) {
-                Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: called on "
+                Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: called on "
                         + "device that doesn't support multiple users on multiple displays",
-                        userId, profileGroupId, foreground, displayId);
+                        userId, profileGroupId, userStartModeToString(userStartMode), displayId);
                 return USER_ASSIGNMENT_RESULT_FAILURE;
             }
         }
 
         if (isProfile(userId, profileGroupId)) {
             if (displayId != DEFAULT_DISPLAY) {
-                Slogf.w(TAG, "canStartUserLocked(%d, %d, %b, %d) failed: cannot start profile user "
-                        + "on secondary display", userId, profileGroupId, foreground,
-                        displayId);
+                Slogf.w(TAG, "canStartUserLocked(%d, %d, %s, %d) failed: cannot start profile user "
+                        + "on secondary display", userId, profileGroupId,
+                        userStartModeToString(userStartMode), displayId);
                 return USER_ASSIGNMENT_RESULT_FAILURE;
             }
-            if (foreground) {
-                Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in "
-                        + "foreground", userId, profileGroupId, foreground, displayId);
-                return USER_ASSIGNMENT_RESULT_FAILURE;
-            } else {
-                boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId);
-                if (DBG) {
-                    Slogf.d(TAG, "parent visible on display: %b", isParentVisibleOnDisplay);
-                }
-                return isParentVisibleOnDisplay
-                        ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
-                        : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+            switch (userStartMode) {
+                case USER_START_MODE_FOREGROUND:
+                    Slogf.w(TAG, "startUser(%d, %d, %s, %d) failed: cannot start profile user in "
+                            + "foreground", userId, profileGroupId,
+                            userStartModeToString(userStartMode), displayId);
+                    return USER_ASSIGNMENT_RESULT_FAILURE;
+                case USER_START_MODE_BACKGROUND_VISIBLE:
+                    boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId);
+                    if (!isParentVisibleOnDisplay) {
+                        Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: cannot"
+                                + " start profile user visible when its parent is not visible in "
+                                + "that display", userId, profileGroupId,
+                                userStartModeToString(userStartMode), displayId);
+                        return USER_ASSIGNMENT_RESULT_FAILURE;
+                    }
+                    return USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+                case USER_START_MODE_BACKGROUND:
+                    return USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
             }
+
         }
 
         return foreground || displayId != DEFAULT_DISPLAY
@@ -338,8 +392,9 @@
             if (mVisibleBackgroundUserOnDefaultDisplayAllowed
                     && userStartMode == USER_START_MODE_BACKGROUND_VISIBLE) {
                 int userStartedOnDefaultDisplay = getUserStartedOnDisplay(DEFAULT_DISPLAY);
-                if (userStartedOnDefaultDisplay != USER_NULL) {
-                    Slogf.w(TAG, "getUserVisibilityOnStartLocked(): cannot start user %d visible on"
+                if (userStartedOnDefaultDisplay != USER_NULL
+                        && userStartedOnDefaultDisplay != profileGroupId) {
+                    Slogf.w(TAG, "canAssignUserToDisplayLocked(): cannot start user %d visible on"
                             + " default display because user %d already did so", userId,
                             userStartedOnDefaultDisplay);
                     return SECONDARY_DISPLAY_MAPPING_FAILED;
@@ -360,7 +415,7 @@
                 // parent is the current user, as the current user is always assigned to the
                 // DEFAULT_DISPLAY).
                 if (DBG) {
-                    Slogf.d(TAG, "ignoring mapping for default display for user %d starting as %s",
+                    Slogf.d(TAG, "Ignoring mapping for default display for user %d starting as %s",
                             userId, userStartModeToString(userStartMode));
                 }
                 return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED;
@@ -445,7 +500,7 @@
                         userId, displayId);
                 return false;
             }
-            if (isStartedProfile(userId)) {
+            if (isStartedVisibleProfileLocked(userId)) {
                 Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because user is a profile",
                         userId, displayId);
                 return false;
@@ -533,10 +588,14 @@
     @GuardedBy("mLock")
     private void unassignUserFromAllDisplaysOnStopLocked(@UserIdInt int userId) {
         if (DBG) {
-            Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
-                    mStartedProfileGroupIds);
+            Slogf.d(TAG, "Removing %d from mStartedVisibleProfileGroupIds (%s)", userId,
+                    mStartedVisibleProfileGroupIds);
         }
-        mStartedProfileGroupIds.delete(userId);
+        mStartedVisibleProfileGroupIds.delete(userId);
+        if (mStartedInvisibleProfileUserIds != null) {
+            Slogf.d(TAG, "Removing %d from list of invisible profiles", userId);
+            mStartedInvisibleProfileUserIds.remove(Integer.valueOf(userId));
+        }
 
         if (!mVisibleBackgroundUsersEnabled) {
             // Don't need to update mUsersAssignedToDisplayOnStart because methods (such as
@@ -566,7 +625,8 @@
      * See {@link UserManagerInternal#isUserVisible(int)}.
      */
     public boolean isUserVisible(@UserIdInt int userId) {
-        // First check current foreground user and their profiles (on main display)
+        // For optimization (as most devices don't support visible background users), check for
+        // current foreground user and their profiles first
         if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
             if (VERBOSE) {
                 Slogf.v(TAG, "isUserVisible(%d): true to current user or profile", userId);
@@ -575,19 +635,31 @@
         }
 
         if (!mVisibleBackgroundUsersEnabled) {
-            if (DBG) {
-                Slogf.d(TAG, "isUserVisible(%d): false for non-current user (or its profiles) when"
+            if (VERBOSE) {
+                Slogf.v(TAG, "isUserVisible(%d): false for non-current user (or its profiles) when"
                         + " device doesn't support visible background users", userId);
             }
             return false;
         }
 
-        boolean visible;
+
         synchronized (mLock) {
-            visible = mUsersAssignedToDisplayOnStart.indexOfKey(userId) >= 0;
+            int profileGroupId;
+            synchronized (mLock) {
+                profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+            }
+            if (isProfile(userId, profileGroupId)) {
+                return isUserAssignedToDisplayOnStartLocked(profileGroupId);
+            }
+            return isUserAssignedToDisplayOnStartLocked(userId);
         }
-        if (DBG) {
-            Slogf.d(TAG, "isUserVisible(%d): %b from mapping", userId, visible);
+    }
+
+    @GuardedBy("mLock")
+    private boolean isUserAssignedToDisplayOnStartLocked(@UserIdInt int userId) {
+        boolean visible = mUsersAssignedToDisplayOnStart.indexOfKey(userId) >= 0;
+        if (VERBOSE) {
+            Slogf.v(TAG, "isUserAssignedToDisplayOnStartLocked(%d): %b", userId, visible);
         }
         return visible;
     }
@@ -600,7 +672,8 @@
             return false;
         }
 
-        // Current user is always visible on:
+        // For optimization (as most devices don't support visible background users), check for
+        // current user and profile first. Current user is always visible on:
         // - Default display
         // - Secondary displays when device doesn't support visible bg users
         //   - Or when explicitly added (which is checked below)
@@ -622,16 +695,28 @@
         }
 
         synchronized (mLock) {
-            if (mUsersAssignedToDisplayOnStart.get(userId, Display.INVALID_DISPLAY) == displayId) {
-                // User assigned to display on start
-                return true;
+            int profileGroupId;
+            synchronized (mLock) {
+                profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
             }
-
-            // Check for extra display assignment
-            return mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL) == userId;
+            if (isProfile(userId, profileGroupId)) {
+                return isFullUserVisibleOnBackgroundLocked(profileGroupId, displayId);
+            }
+            return isFullUserVisibleOnBackgroundLocked(userId, displayId);
         }
     }
 
+    // NOTE: it doesn't check if the userId is a full user, it's up to the caller to check that
+    @GuardedBy("mLock")
+    private boolean isFullUserVisibleOnBackgroundLocked(@UserIdInt int userId, int displayId) {
+        if (mUsersAssignedToDisplayOnStart.get(userId, Display.INVALID_DISPLAY) == displayId) {
+            // User assigned to display on start
+            return true;
+        }
+        // Check for extra display assignment
+        return mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL) == userId;
+    }
+
     /**
      * See {@link UserManagerInternal#getDisplayAssignedToUser(int)}.
      */
@@ -697,7 +782,7 @@
                     continue;
                 }
                 int userId = mUsersAssignedToDisplayOnStart.keyAt(i);
-                if (!isStartedProfile(userId)) {
+                if (!isStartedVisibleProfileLocked(userId)) {
                     return userId;
                 } else if (DBG) {
                     Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
@@ -730,8 +815,8 @@
         // number of users is too small, the gain is probably not worth the increase on complexity.
         IntArray visibleUsers = new IntArray();
         synchronized (mLock) {
-            for (int i = 0; i < mStartedProfileGroupIds.size(); i++) {
-                int userId = mStartedProfileGroupIds.keyAt(i);
+            for (int i = 0; i < mStartedVisibleProfileGroupIds.size(); i++) {
+                int userId = mStartedVisibleProfileGroupIds.keyAt(i);
                 if (isUserVisible(userId)) {
                     visibleUsers.add(userId);
                 }
@@ -764,7 +849,7 @@
         }
     }
 
-    // TODO(b/242195409): remove this method if not needed anymore
+    // TODO(b/266158156): remove this method if not needed anymore
     /**
      * Nofify all listeners that the system user visibility changed.
      */
@@ -826,6 +911,9 @@
         ipw.println("UserVisibilityMediator");
         ipw.increaseIndent();
 
+        ipw.print("DBG: ");
+        ipw.println(DBG);
+
         synchronized (mLock) {
             ipw.print("Current user id: ");
             ipw.println(mCurrentUserId);
@@ -833,8 +921,12 @@
             ipw.print("Visible users: ");
             ipw.println(getVisibleUsers());
 
-            dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group",
-                    "u", "pg");
+            dumpSparseIntArray(ipw, mStartedVisibleProfileGroupIds,
+                    "started visible user / profile group", "u", "pg");
+            if (mStartedInvisibleProfileUserIds != null) {
+                ipw.print("Profiles started invisible: ");
+                ipw.println(mStartedInvisibleProfileUserIds);
+            }
 
             ipw.print("Supports visible background users on displays: ");
             ipw.println(mVisibleBackgroundUsersEnabled);
@@ -924,6 +1016,15 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private boolean isCurrentUserLocked(@UserIdInt int userId) {
+        // Special case as NO_PROFILE_GROUP_ID == USER_NULL
+        if (userId == USER_NULL || mCurrentUserId == USER_NULL) {
+            return false;
+        }
+        return mCurrentUserId == userId;
+    }
+
     private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
         synchronized (mLock) {
             // Special case as NO_PROFILE_GROUP_ID == USER_NULL
@@ -933,22 +1034,25 @@
             if (mCurrentUserId == userId) {
                 return true;
             }
-            return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID) == mCurrentUserId;
+            return mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID)
+                    == mCurrentUserId;
         }
     }
 
-    private boolean isStartedProfile(@UserIdInt int userId) {
-        int profileGroupId;
-        synchronized (mLock) {
-            profileGroupId = mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
-        }
+    @GuardedBy("mLock")
+    private boolean isStartedVisibleProfileLocked(@UserIdInt int userId) {
+        int profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
         return isProfile(userId, profileGroupId);
     }
 
-    private @UserIdInt int getStartedProfileGroupId(@UserIdInt int userId) {
-        synchronized (mLock) {
-            return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+    private void validateUserStartMode(@UserStartMode int userStartMode) {
+        switch (userStartMode) {
+            case USER_START_MODE_FOREGROUND:
+            case USER_START_MODE_BACKGROUND:
+            case USER_START_MODE_BACKGROUND_VISIBLE:
+                return;
         }
+        throw new IllegalArgumentException("Invalid user start mode: " + userStartMode);
     }
 
     private static String secondaryDisplayMappingStatusToString(
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 8ec6241..5b967ec 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -681,12 +681,15 @@
         }
 
         final int installerUid = mVerificationInfo == null ? -1 : mVerificationInfo.mInstallerUid;
+        final boolean requestedDisableVerification =
+                (mInstallFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0;
 
         // Check if installing from ADB
         if ((mInstallFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
-            boolean requestedDisableVerification =
-                    (mInstallFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0;
             return isAdbVerificationEnabled(pkgInfoLite, userId, requestedDisableVerification);
+        } else if (requestedDisableVerification) {
+            // Skip verification for non-adb installs
+            return false;
         }
 
         // only when not installed from ADB, skip verification for instant apps when
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 287cc29..42a8cb1 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -51,7 +51,7 @@
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
-import android.healthconnect.HealthConnectManager;
+import android.health.connect.HealthConnectManager;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Process;
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 7f733ef..8d7f782 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -366,12 +366,12 @@
             }
             mListener = listener;
         }
-        notifySupportedStatesChanged();
+        notifySupportedStatesChanged(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
         notifyDeviceStateChangedIfNeeded();
     }
 
     /** Notifies the listener that the set of supported device states has changed. */
-    private void notifySupportedStatesChanged() {
+    private void notifySupportedStatesChanged(@SupportedStatesUpdatedReason int reason) {
         List<DeviceState> supportedStates = new ArrayList<>();
         Listener listener;
         synchronized (mLock) {
@@ -390,7 +390,7 @@
         }
 
         listener.onSupportedDeviceStatesChanged(
-                supportedStates.toArray(new DeviceState[supportedStates.size()]));
+                supportedStates.toArray(new DeviceState[supportedStates.size()]), reason);
     }
 
     /** Computes the current device state and notifies the listener of a change, if needed. */
@@ -688,7 +688,10 @@
         if (isThermalStatusCriticalOrAbove != isPreviousThermalStatusCriticalOrAbove) {
             Slog.i(TAG, "Updating supported device states due to thermal status change."
                     + " isThermalStatusCriticalOrAbove: " + isThermalStatusCriticalOrAbove);
-            notifySupportedStatesChanged();
+            notifySupportedStatesChanged(
+                    isThermalStatusCriticalOrAbove
+                            ? SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
+                            : SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL);
         }
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 111b4f6..5fd70a8 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -243,6 +243,8 @@
         }
         builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability);
 
+        builder.setUseLocationEnabled(mLocationEnabledSetting);
+
         boolean deviceHasLocationTimeZoneDetection = isGeoDetectionSupported();
         boolean deviceHasTelephonyDetection = isTelephonyDetectionSupported();
 
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
new file mode 100644
index 0000000..53f04d7
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
+import android.os.IBinder;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.util.SparseArray;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Function;
+
+/**
+ * Represents a vibration defined by a {@link CombinedVibration} that will be performed by
+ * the IVibrator HAL.
+ */
+final class HalVibration extends Vibration {
+
+    public final VibrationAttributes attrs;
+    public final long id;
+    public final int uid;
+    public final int displayId;
+    public final String opPkg;
+    public final String reason;
+    public final IBinder token;
+    public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
+
+    /** The actual effect to be played. */
+    @Nullable
+    private CombinedVibration mEffect;
+
+    /**
+     * The original effect that was requested. Typically these two things differ because the effect
+     * was scaled based on the users vibration intensity settings.
+     */
+    @Nullable
+    private CombinedVibration mOriginalEffect;
+
+    /** Vibration status. */
+    private Vibration.Status mStatus;
+
+    /** Vibration runtime stats. */
+    private final VibrationStats mStats = new VibrationStats();
+
+    /** A {@link CountDownLatch} to enable waiting for completion. */
+    private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
+
+    HalVibration(IBinder token, int id, CombinedVibration effect,
+            VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason) {
+        this.token = token;
+        this.mEffect = effect;
+        this.id = id;
+        this.attrs = attrs;
+        this.uid = uid;
+        this.displayId = displayId;
+        this.opPkg = opPkg;
+        this.reason = reason;
+        mStatus = Vibration.Status.RUNNING;
+    }
+
+    VibrationStats stats() {
+        return mStats;
+    }
+
+    /**
+     * Set the {@link Status} of this vibration and reports the current system time as this
+     * vibration end time, for debugging purposes.
+     *
+     * <p>This method will only accept given value if the current status is {@link
+     * Status#RUNNING}.
+     */
+    public void end(EndInfo info) {
+        if (hasEnded()) {
+            // Vibration already ended, keep first ending status set and ignore this one.
+            return;
+        }
+        mStatus = info.status;
+        mStats.reportEnded(info.endedByUid, info.endedByUsage);
+        mCompletionLatch.countDown();
+    }
+
+    /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
+    public void waitForEnd() throws InterruptedException {
+        mCompletionLatch.await();
+    }
+
+    /**
+     * Return the effect to be played when given prebaked effect id is not supported by the
+     * vibrator.
+     */
+    @Nullable
+    public VibrationEffect getFallback(int effectId) {
+        return mFallbacks.get(effectId);
+    }
+
+    /**
+     * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
+     * which might be necessary for replacement in realtime.
+     */
+    public void addFallback(int effectId, VibrationEffect effect) {
+        mFallbacks.put(effectId, effect);
+    }
+
+    /**
+     * Applied update function to the current effect held by this vibration, and to each fallback
+     * effect added.
+     */
+    public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
+        CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn);
+        if (!newEffect.equals(mEffect)) {
+            if (mOriginalEffect == null) {
+                mOriginalEffect = mEffect;
+            }
+            mEffect = newEffect;
+        }
+        for (int i = 0; i < mFallbacks.size(); i++) {
+            mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
+        }
+    }
+
+    /**
+     * Creates a new {@link CombinedVibration} by applying the given transformation function
+     * to each {@link VibrationEffect}.
+     */
+    private static CombinedVibration transformCombinedEffect(
+            CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
+        if (combinedEffect instanceof CombinedVibration.Mono) {
+            VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect();
+            return CombinedVibration.createParallel(fn.apply(effect));
+        } else if (combinedEffect instanceof CombinedVibration.Stereo) {
+            SparseArray<VibrationEffect> effects =
+                    ((CombinedVibration.Stereo) combinedEffect).getEffects();
+            CombinedVibration.ParallelCombination combination =
+                    CombinedVibration.startParallel();
+            for (int i = 0; i < effects.size(); i++) {
+                combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
+            }
+            return combination.combine();
+        } else if (combinedEffect instanceof CombinedVibration.Sequential) {
+            List<CombinedVibration> effects =
+                    ((CombinedVibration.Sequential) combinedEffect).getEffects();
+            CombinedVibration.SequentialCombination combination =
+                    CombinedVibration.startSequential();
+            for (CombinedVibration effect : effects) {
+                combination.addNext(transformCombinedEffect(effect, fn));
+            }
+            return combination.combine();
+        } else {
+            // Unknown combination, return same effect.
+            return combinedEffect;
+        }
+    }
+
+    /** Return true is current status is different from {@link Status#RUNNING}. */
+    public boolean hasEnded() {
+        return mStatus != Status.RUNNING;
+    }
+
+    /** Return true is effect is a repeating vibration. */
+    public boolean isRepeating() {
+        return mEffect.getDuration() == Long.MAX_VALUE;
+    }
+
+    /** Return the effect that should be played by this vibration. */
+    @Nullable
+    public CombinedVibration getEffect() {
+        return mEffect;
+    }
+
+    /**
+     * Return {@link Vibration.DebugInfo} with read-only debug information about this vibration.
+     */
+    public Vibration.DebugInfo getDebugInfo() {
+        return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
+                attrs, uid, displayId, opPkg, reason);
+    }
+
+    /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
+    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+        int vibrationType = isRepeating()
+                ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
+                : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+        return new VibrationStats.StatsInfo(
+                uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
+    }
+
+    /**
+     * Returns true if this vibration can pipeline with the specified one.
+     *
+     * <p>Note that currently, repeating vibrations can't pipeline with following vibrations,
+     * because the cancel() call to stop the repetition will cancel a pending vibration too. This
+     * can be changed if we have a use-case to reason around behavior for. It may also be nice to
+     * pipeline very short vibrations together, regardless of the flag.
+     */
+    public boolean canPipelineWith(HalVibration vib) {
+        return uid == vib.uid && attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
+                && vib.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
+                && !isRepeating();
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/Step.java b/services/core/java/com/android/server/vibrator/Step.java
index 042e8a0..5faa516 100644
--- a/services/core/java/com/android/server/vibrator/Step.java
+++ b/services/core/java/com/android/server/vibrator/Step.java
@@ -38,7 +38,7 @@
         this.startTime = startTime;
     }
 
-    protected Vibration getVibration() {
+    protected HalVibration getVibration() {
         return conductor.getVibration();
     }
 
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index a451ee2..5667c72 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.CombinedVibration;
-import android.os.IBinder;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.vibrator.PrebakedSegment;
@@ -27,20 +26,16 @@
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
-import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.util.FrameworkStatsLog;
-
 import java.text.SimpleDateFormat;
 import java.util.Date;
-import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.function.Function;
 
-/** Represents a vibration request to the vibrator service. */
-final class Vibration {
+/**
+ * The base class for all vibrations.
+ */
+class Vibration {
     private static final SimpleDateFormat DEBUG_DATE_FORMAT =
             new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
@@ -85,186 +80,6 @@
         }
     }
 
-    public final VibrationAttributes attrs;
-    public final long id;
-    public final int uid;
-    public final int displayId;
-    public final String opPkg;
-    public final String reason;
-    public final IBinder token;
-    public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
-
-    /** The actual effect to be played. */
-    @Nullable
-    private CombinedVibration mEffect;
-
-    /**
-     * The original effect that was requested. Typically these two things differ because the effect
-     * was scaled based on the users vibration intensity settings.
-     */
-    @Nullable
-    private CombinedVibration mOriginalEffect;
-
-    /** Vibration status. */
-    private Vibration.Status mStatus;
-
-    /** Vibration runtime stats. */
-    private final VibrationStats mStats = new VibrationStats();
-
-    /** A {@link CountDownLatch} to enable waiting for completion. */
-    private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
-
-    Vibration(IBinder token, int id, CombinedVibration effect,
-            VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason) {
-        this.token = token;
-        this.mEffect = effect;
-        this.id = id;
-        this.attrs = attrs;
-        this.uid = uid;
-        this.displayId = displayId;
-        this.opPkg = opPkg;
-        this.reason = reason;
-        mStatus = Vibration.Status.RUNNING;
-    }
-
-    VibrationStats stats() {
-        return mStats;
-    }
-
-    /**
-     * Set the {@link Status} of this vibration and reports the current system time as this
-     * vibration end time, for debugging purposes.
-     *
-     * <p>This method will only accept given value if the current status is {@link
-     * Status#RUNNING}.
-     */
-    public void end(EndInfo info) {
-        if (hasEnded()) {
-            // Vibration already ended, keep first ending status set and ignore this one.
-            return;
-        }
-        mStatus = info.status;
-        mStats.reportEnded(info.endedByUid, info.endedByUsage);
-        mCompletionLatch.countDown();
-    }
-
-    /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
-    public void waitForEnd() throws InterruptedException {
-        mCompletionLatch.await();
-    }
-
-    /**
-     * Return the effect to be played when given prebaked effect id is not supported by the
-     * vibrator.
-     */
-    @Nullable
-    public VibrationEffect getFallback(int effectId) {
-        return mFallbacks.get(effectId);
-    }
-
-    /**
-     * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
-     * which might be necessary for replacement in realtime.
-     */
-    public void addFallback(int effectId, VibrationEffect effect) {
-        mFallbacks.put(effectId, effect);
-    }
-
-    /**
-     * Applied update function to the current effect held by this vibration, and to each fallback
-     * effect added.
-     */
-    public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
-        CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn);
-        if (!newEffect.equals(mEffect)) {
-            if (mOriginalEffect == null) {
-                mOriginalEffect = mEffect;
-            }
-            mEffect = newEffect;
-        }
-        for (int i = 0; i < mFallbacks.size(); i++) {
-            mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
-        }
-    }
-
-    /**
-     * Creates a new {@link CombinedVibration} by applying the given transformation function
-     * to each {@link VibrationEffect}.
-     */
-    private static CombinedVibration transformCombinedEffect(
-            CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
-        if (combinedEffect instanceof CombinedVibration.Mono) {
-            VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect();
-            return CombinedVibration.createParallel(fn.apply(effect));
-        } else if (combinedEffect instanceof CombinedVibration.Stereo) {
-            SparseArray<VibrationEffect> effects =
-                    ((CombinedVibration.Stereo) combinedEffect).getEffects();
-            CombinedVibration.ParallelCombination combination =
-                    CombinedVibration.startParallel();
-            for (int i = 0; i < effects.size(); i++) {
-                combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
-            }
-            return combination.combine();
-        } else if (combinedEffect instanceof CombinedVibration.Sequential) {
-            List<CombinedVibration> effects =
-                    ((CombinedVibration.Sequential) combinedEffect).getEffects();
-            CombinedVibration.SequentialCombination combination =
-                    CombinedVibration.startSequential();
-            for (CombinedVibration effect : effects) {
-                combination.addNext(transformCombinedEffect(effect, fn));
-            }
-            return combination.combine();
-        } else {
-            // Unknown combination, return same effect.
-            return combinedEffect;
-        }
-    }
-
-    /** Return true is current status is different from {@link Status#RUNNING}. */
-    public boolean hasEnded() {
-        return mStatus != Status.RUNNING;
-    }
-
-    /** Return true is effect is a repeating vibration. */
-    public boolean isRepeating() {
-        return mEffect.getDuration() == Long.MAX_VALUE;
-    }
-
-    /** Return the effect that should be played by this vibration. */
-    @Nullable
-    public CombinedVibration getEffect() {
-        return mEffect;
-    }
-
-    /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
-    public Vibration.DebugInfo getDebugInfo() {
-        return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
-                attrs, uid, displayId, opPkg, reason);
-    }
-
-    /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
-    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
-        int vibrationType = isRepeating()
-                ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
-                : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
-        return new VibrationStats.StatsInfo(
-                uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
-    }
-
-    /**
-     * Returns true if this vibration can pipeline with the specified one.
-     *
-     * <p>Note that currently, repeating vibrations can't pipeline with following vibrations,
-     * because the cancel() call to stop the repetition will cancel a pending vibration too. This
-     * can be changed if we have a use-case to reason around behavior for. It may also be nice to
-     * pipeline very short vibrations together, regardless of the flag.
-     */
-    public boolean canPipelineWith(Vibration vib) {
-        return uid == vib.uid && attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
-                && vib.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
-                && !isRepeating();
-    }
-
     /** Immutable info passed as a signal to end a vibration. */
     static final class EndInfo {
         /** The {@link Status} to be set to the vibration when it ends with this info. */
@@ -505,5 +320,4 @@
             proto.end(token);
         }
     }
-
 }
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 141be70..11cab4b 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -68,7 +68,7 @@
     // Not guarded by lock because they're not modified by this conductor, it's used here only to
     // check immutable attributes. The status and other mutable states are changed by the service or
     // by the vibrator steps.
-    private final Vibration mVibration;
+    private final HalVibration mVibration;
     private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
 
     private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
@@ -95,7 +95,7 @@
     private int mRemainingStartSequentialEffectSteps;
     private int mSuccessfulVibratorOnSteps;
 
-    VibrationStepConductor(Vibration vib, VibrationSettings vibrationSettings,
+    VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
             DeviceVibrationEffectAdapter effectAdapter,
             SparseArray<VibratorController> availableVibrators,
             VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
@@ -160,7 +160,7 @@
         mVibration.stats().reportStarted();
     }
 
-    public Vibration getVibration() {
+    public HalVibration getVibration() {
         // No thread assertion: immutable
         return mVibration;
     }
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 3fd9594..d2dc43c 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -33,12 +33,12 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 
-/** Plays a {@link Vibration} in dedicated thread. */
+/** Plays a {@link HalVibration} in dedicated thread. */
 final class VibrationThread extends Thread {
     static final String TAG = "VibrationThread";
     static final boolean DEBUG = false;
 
-    /** Calls into VibratorManager functionality needed for playing a {@link Vibration}. */
+    /** Calls into VibratorManager functionality needed for playing a {@link HalVibration}. */
     interface VibratorManagerHooks {
 
         /**
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 966329e..c87332d 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -385,12 +385,13 @@
     }
 
     /**
-     * An internal-only version of vibrate that allows the caller access to the {@link Vibration}.
+     * An internal-only version of vibrate that allows the caller access to the
+     * {@link HalVibration}.
      * The Vibration is only returned if it is ongoing after this method returns.
      */
     @VisibleForTesting
     @Nullable
-    Vibration vibrateInternal(int uid, int displayId, String opPkg,
+    HalVibration vibrateInternal(int uid, int displayId, String opPkg,
             @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
             String reason, IBinder token) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
@@ -407,8 +408,8 @@
             }
             attrs = fixupVibrationAttributes(attrs, effect);
             // Create Vibration.Stats as close to the received request as possible, for tracking.
-            Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
-                    uid, displayId, opPkg, reason);
+            HalVibration vib = new HalVibration(token, mNextVibrationId.getAndIncrement(), effect,
+                    attrs, uid, displayId, opPkg, reason);
             fillVibrationFallbacks(vib, effect);
 
             if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
@@ -639,7 +640,7 @@
                 return;
             }
 
-            Vibration vib = mCurrentVibration.getVibration();
+            HalVibration vib = mCurrentVibration.getVibration();
             Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
                     vib.uid, vib.displayId, vib.opPkg, vib.attrs);
 
@@ -683,7 +684,7 @@
     }
 
     @GuardedBy("mLock")
-    private Vibration.Status startVibrationLocked(Vibration vib) {
+    private Vibration.Status startVibrationLocked(HalVibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
         try {
             vib.updateEffects(effect -> mVibrationScaler.scale(effect, vib.attrs.getUsage()));
@@ -716,7 +717,7 @@
     private Vibration.Status startVibrationOnThreadLocked(VibrationStepConductor conductor) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
         try {
-            Vibration vib = conductor.getVibration();
+            HalVibration vib = conductor.getVibration();
             int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs);
             switch (mode) {
                 case AppOpsManager.MODE_ALLOWED:
@@ -741,7 +742,7 @@
     }
 
     @GuardedBy("mLock")
-    private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo,
+    private void endVibrationLocked(HalVibration vib, Vibration.EndInfo vibrationEndInfo,
             boolean shouldWriteStats) {
         vib.end(vibrationEndInfo);
         logVibrationStatus(vib.uid, vib.attrs, vibrationEndInfo.status);
@@ -763,7 +764,8 @@
                 vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
     }
 
-    private void logVibrationStatus(int uid, VibrationAttributes attrs, Vibration.Status status) {
+    private void logVibrationStatus(int uid, VibrationAttributes attrs,
+            Vibration.Status status) {
         switch (status) {
             case IGNORED_BACKGROUND:
                 Slog.e(TAG, "Ignoring incoming vibration as process with"
@@ -813,7 +815,7 @@
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
         try {
-            Vibration vib = mCurrentVibration.getVibration();
+            HalVibration vib = mCurrentVibration.getVibration();
             if (DEBUG) {
                 Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vibrationEndInfo);
             }
@@ -857,13 +859,13 @@
      */
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.Status shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
+    private Vibration.Status shouldIgnoreVibrationForOngoingLocked(HalVibration vib) {
         if (mCurrentVibration == null || vib.isRepeating()) {
             // Incoming repeating vibrations always take precedence over ongoing vibrations.
             return null;
         }
 
-        Vibration currentVibration = mCurrentVibration.getVibration();
+        HalVibration currentVibration = mCurrentVibration.getVibration();
         if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
             // Current vibration has ended or is cancelling, should not block incoming vibrations.
             return null;
@@ -945,7 +947,7 @@
      * @param token       The binder token to identify the vibration origin. Only vibrations
      *                    started with the same token can be cancelled with it.
      */
-    private boolean shouldCancelVibration(Vibration vib, int usageFilter, IBinder token) {
+    private boolean shouldCancelVibration(HalVibration vib, int usageFilter, IBinder token) {
         return (vib.token == token) && shouldCancelVibration(vib.attrs, usageFilter);
     }
 
@@ -1041,7 +1043,7 @@
      * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
      * VibrationSettings#getFallbackEffect}.
      */
-    private void fillVibrationFallbacks(Vibration vib, CombinedVibration effect) {
+    private void fillVibrationFallbacks(HalVibration vib, CombinedVibration effect) {
         if (effect instanceof CombinedVibration.Mono) {
             fillVibrationFallbacks(vib, ((CombinedVibration.Mono) effect).getEffect());
         } else if (effect instanceof CombinedVibration.Stereo) {
@@ -1059,7 +1061,7 @@
         }
     }
 
-    private void fillVibrationFallbacks(Vibration vib, VibrationEffect effect) {
+    private void fillVibrationFallbacks(HalVibration vib, VibrationEffect effect) {
         VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
         int segmentCount = composed.getSegments().size();
         for (int i = 0; i < segmentCount; i++) {
@@ -1183,7 +1185,7 @@
         if (conductor == null) {
             return false;
         }
-        Vibration vib = conductor.getVibration();
+        HalVibration vib = conductor.getVibration();
         return mVibrationSettings.shouldCancelVibrationOnScreenOff(
                 vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.stats().getCreateUptimeMillis());
     }
@@ -1535,7 +1537,7 @@
             mPreviousVibrationsLimit = limit;
         }
 
-        synchronized void record(Vibration vib) {
+        synchronized void record(HalVibration vib) {
             int usage = vib.attrs.getUsage();
             if (!mPreviousVibrations.contains(usage)) {
                 mPreviousVibrations.put(usage, new LinkedList<>());
@@ -1870,7 +1872,7 @@
             // only cancel background vibrations.
             IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
                     : mShellCallbacksToken;
-            Vibration vib = vibrateInternal(Binder.getCallingUid(), Display.DEFAULT_DISPLAY,
+            HalVibration vib = vibrateInternal(Binder.getCallingUid(), Display.DEFAULT_DISPLAY,
                     SHELL_PACKAGE_NAME, combined, attrs, commonOptions.description, deathBinder);
             if (vib != null && !commonOptions.background) {
                 try {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f183760..f592f9a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1756,12 +1756,9 @@
             return ROTATION_UNDEFINED;
         }
         if (activityOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
-            // TODO(b/266280737): Use ActivityRecord#canDefineOrientationForActivitiesAbove
             final ActivityRecord nextCandidate = getActivity(
-                    a -> a.getOverrideOrientation() != SCREEN_ORIENTATION_UNSET
-                            && a.getOverrideOrientation()
-                                    != ActivityInfo.SCREEN_ORIENTATION_BEHIND,
-                    r, false /* includeBoundary */, true /* traverseTopToBottom */);
+                    a -> a.canDefineOrientationForActivitiesAbove() /* callback */,
+                    r /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */);
             if (nextCandidate != null) {
                 r = nextCandidate;
             }
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 800fe09..b64420a 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST;
 import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
 
 import android.annotation.IntDef;
@@ -311,12 +312,23 @@
         mDeviceConfig.updateFlagActiveStatus(
                 /* isActive */ mIsDisplayRotationImmersiveAppCompatPolicyEnabled,
                 /* key */ KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
+        mDeviceConfig.updateFlagActiveStatus(
+                /* isActive */ true,
+                /* key */ KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
 
         mLetterboxConfigurationPersister = letterboxConfigurationPersister;
         mLetterboxConfigurationPersister.start();
     }
 
     /**
+     * Whether enabling ignoreOrientationRequest is allowed on the device. This value is controlled
+     * via {@link android.provider.DeviceConfig}.
+     */
+    boolean isIgnoreOrientationRequestAllowed() {
+        return mDeviceConfig.getFlag(KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
+    }
+
+    /**
      * Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link
      * #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link
      * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
index cf123a1..3f067e3 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
@@ -38,10 +38,16 @@
     private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
             true;
 
+    static final String KEY_ALLOW_IGNORE_ORIENTATION_REQUEST =
+            "allow_ignore_orientation_request";
+    private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true;
+
     @VisibleForTesting
     static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
             KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
-            DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY
+            DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
+            KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
+            DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST
     );
 
     // Whether enabling rotation compat policy for immersive apps that prevents auto rotation
@@ -52,6 +58,10 @@
     private boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
             DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
 
+    // Whether enabling ignoreOrientationRequest is allowed on the device.
+    private boolean mIsAllowIgnoreOrientationRequest =
+            DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST;
+
     // Set of active device configs that need to be updated in
     // DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
     private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
@@ -93,6 +103,8 @@
         switch (key) {
             case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
                 return mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
+            case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
+                return mIsAllowIgnoreOrientationRequest;
             default:
                 throw new AssertionError("Unexpected flag name: " + key);
         }
@@ -108,6 +120,10 @@
                 mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
                         getDeviceConfig(key, defaultValue);
                 break;
+            case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
+                mIsAllowIgnoreOrientationRequest =
+                        getDeviceConfig(key, defaultValue);
+                break;
             default:
                 throw new AssertionError("Unexpected flag name: " + key);
         }
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 210d5a5..6b2bf59 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -99,7 +99,6 @@
         }
     }
 
-    /** Returns {@code true} if visibility is changed. */
     void updateWallpaperWindows(boolean visible) {
         boolean changed = false;
         if (mVisibleRequested != visible) {
@@ -117,6 +116,10 @@
                     linkFixedRotationTransform(wallpaperTarget.mToken);
                 }
             }
+            // If wallpaper is in transition, setVisible() will be called from commitVisibility()
+            // when finishing transition. Otherwise commitVisibility() is already called from above
+            // setVisibility().
+            return;
         }
 
         final WindowState wallpaperTarget =
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a596eed..6eab745 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4210,7 +4210,8 @@
      * <p>Note: this assumes that {@link #mGlobalLock} is held by the caller.
      */
     boolean isIgnoreOrientationRequestDisabled() {
-        return mIsIgnoreOrientationRequestDisabled;
+        return mIsIgnoreOrientationRequestDisabled
+                || !mLetterboxConfiguration.isIgnoreOrientationRequestAllowed();
     }
 
     @Override
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index ec42324..db7fced 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -23,9 +23,11 @@
 
 namespace android {
 
-static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure) {
+static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure,
+                                   jfloat requestedRefreshRate) {
     ScopedUtfChars name(env, nameObj);
-    sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure)));
+    sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure),
+                                                           requestedRefreshRate));
     return javaObjectForIBinder(env, token);
 }
 
@@ -134,7 +136,7 @@
 
 static const JNINativeMethod sDisplayMethods[] = {
         // clang-format off
-    {"nativeCreateDisplay", "(Ljava/lang/String;Z)Landroid/os/IBinder;",
+    {"nativeCreateDisplay", "(Ljava/lang/String;ZF)Landroid/os/IBinder;",
             (void*)nativeCreateDisplay },
     {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
             (void*)nativeDestroyDisplay },
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 36cb898..d7a2095 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -1441,8 +1441,7 @@
         if (mManagedSubscriptionsPolicy != null) {
             pw.println("mManagedSubscriptionsPolicy:");
             pw.increaseIndent();
-            pw.print("mPolicyType=");
-            mManagedSubscriptionsPolicy.getPolicyType();
+            pw.println(mManagedSubscriptionsPolicy);
             pw.decreaseIndent();
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9c64054..c67ffd5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -20254,6 +20254,17 @@
                         DevicePolicyManager.MTE_ENABLED);
         Preconditions.checkArgument(
                 allowedModes.contains(flags), "Provided mode is not one of the allowed values.");
+        // In general, this API should be available when "bootctl_settings_toggle" is set, which
+        // signals that there is a control for MTE in the user settings and this API fundamentally
+        // is a way for the device admin to override that setting.
+        // Allow bootctl_device_policy_manager as an override, e.g. to offer the
+        // DevicePolicyManager only without a visible user setting.
+        if (!mInjector.systemPropertiesGetBoolean(
+                "ro.arm64.memtag.bootctl_device_policy_manager",
+                mInjector.systemPropertiesGetBoolean(
+                        "ro.arm64.memtag.bootctl_settings_toggle", false))) {
+            throw new UnsupportedOperationException("device does not support MTE");
+        }
         final CallerIdentity caller = getCallerIdentity();
         if (flags == DevicePolicyManager.MTE_DISABLED) {
             Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 720f486..7e44049 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
+import android.os.Binder;
 import android.os.RemoteException;
 import android.view.inputmethod.InputMethodManager;
 
@@ -66,8 +67,8 @@
     @Test
     public void testPerformShowIme() throws Exception {
         synchronized (ImfLock.class) {
-            mVisibilityApplier.performShowIme(mWindowToken, null /* statsToken */,
-                    InputMethodManager.SHOW_IMPLICIT, null, SHOW_SOFT_INPUT);
+            mVisibilityApplier.performShowIme(new Binder() /* showInputToken */,
+                    null /* statsToken */, InputMethodManager.SHOW_IMPLICIT, null, SHOW_SOFT_INPUT);
         }
         verifyShowSoftInput(false, true, InputMethodManager.SHOW_IMPLICIT);
     }
@@ -75,8 +76,8 @@
     @Test
     public void testPerformHideIme() throws Exception {
         synchronized (ImfLock.class) {
-            mVisibilityApplier.performHideIme(mWindowToken, null /* statsToken */, null,
-                    HIDE_SOFT_INPUT);
+            mVisibilityApplier.performHideIme(new Binder() /* hideInputToken */,
+                    null /* statsToken */, null, HIDE_SOFT_INPUT);
         }
         verifyHideSoftInput(false, true);
     }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 75484d1..a39e021 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1129,6 +1129,7 @@
                 false /*allowInstall*/,
                 false /*instantApp*/,
                 false /*virtualPreload*/,
+                false /* stopped */,
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
@@ -1173,6 +1174,7 @@
                 true /*allowInstall*/,
                 false /*instantApp*/,
                 false /*virtualPreload*/,
+                false /* stopped */,
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
@@ -1217,6 +1219,7 @@
                 false /*allowInstall*/,
                 false /*instantApp*/,
                 false /*virtualPreload*/,
+                false /* stopped */,
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
@@ -1262,6 +1265,7 @@
                 false /*allowInstall*/,
                 false /*instantApp*/,
                 false /*virtualPreload*/,
+                false /* stopped */,
                 UserManagerService.getInstance(),
                 null /*usesSdkLibraries*/,
                 null /*usesSdkLibrariesVersions*/,
@@ -1284,6 +1288,48 @@
         verifyUserState(userState, false /*notLaunched*/, false /*stopped*/, true /*installed*/);
     }
 
+    /** Create a new stopped system PackageSetting */
+    @Test
+    public void testCreateNewSetting05() {
+        final PackageSetting testPkgSetting01 = Settings.createNewSetting(
+                PACKAGE_NAME,
+                null /*originalPkg*/,
+                null /*disabledPkg*/,
+                null /*realPkgName*/,
+                null /*sharedUser*/,
+                UPDATED_CODE_PATH /*codePath*/,
+                null /*legacyNativeLibraryPath*/,
+                "arm64-v8a" /*primaryCpuAbi*/,
+                "armeabi" /*secondaryCpuAbi*/,
+                UPDATED_VERSION_CODE /*versionCode*/,
+                ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
+                0 /*pkgPrivateFlags*/,
+                UserHandle.SYSTEM /*installUser*/,
+                false /*allowInstall*/,
+                false /*instantApp*/,
+                false /*virtualPreload*/,
+                true /* stopped */,
+                UserManagerService.getInstance(),
+                null /*usesSdkLibraries*/,
+                null /*usesSdkLibrariesVersions*/,
+                null /*usesStaticLibraries*/,
+                null /*usesStaticLibrariesVersions*/,
+                null /*mimeGroups*/,
+                UUID.randomUUID());
+        assertThat(testPkgSetting01.getAppId(), is(0));
+        assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
+        assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
+        assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
+        assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+        assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
+        assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
+        assertThat(testPkgSetting01.getVersionCode(), is(UPDATED_VERSION_CODE));
+        final PackageUserState userState = testPkgSetting01.readUserState(0);
+        verifyUserState(userState, false /*notLaunched*/, true /*stopped*/, true /*installed*/);
+    }
+
     @Test
     public void testSetPkgStateLibraryFiles_addNewFiles() {
         final PackageSetting packageSetting = createPackageSetting("com.foo");
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index ef470fe..3b20735 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -112,7 +112,6 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -1265,7 +1264,30 @@
         mService.interactiveStateChangedLocked(false);
         mService.interactiveStateChangedLocked(true);
         runnableCaptor.getValue().run();
-        verify(mMockContext).sendBroadcastAsUser(mService.mTimeTickIntent, UserHandle.ALL);
+        final ArgumentCaptor<Bundle> optionsCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mMockContext).sendBroadcastAsUser(eq(mService.mTimeTickIntent), eq(UserHandle.ALL),
+                isNull(), optionsCaptor.capture());
+        verifyTimeTickBroadcastOptions(optionsCaptor.getValue());
+    }
+
+    @Test
+    public void sendsTimeTickOnAlarmTrigger() throws Exception {
+        final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        // Stubbing so the handler doesn't actually run the runnable.
+        doReturn(true).when(mService.mHandler).post(runnableCaptor.capture());
+        mService.mTimeTickTrigger.doAlarm(mock(IAlarmCompleteListener.class));
+        runnableCaptor.getValue().run();
+        final ArgumentCaptor<Bundle> optionsCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mMockContext).sendBroadcastAsUser(eq(mService.mTimeTickIntent), eq(UserHandle.ALL),
+                isNull(), optionsCaptor.capture());
+        verifyTimeTickBroadcastOptions(optionsCaptor.getValue());
+    }
+
+    private void verifyTimeTickBroadcastOptions(Bundle actualOptionsBundle) {
+        final BroadcastOptions actualOptions = new BroadcastOptions(actualOptionsBundle);
+        assertEquals(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT,
+                actualOptions.getDeliveryGroupPolicy());
+        assertTrue(actualOptions.isDeferUntilActive());
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index c40017a..2fb6c23 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -110,6 +110,7 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -225,6 +226,31 @@
         }
     }
 
+    /**
+     * Replace the process LRU with the given processes.
+     * @param apps
+     */
+    private void setProcessesToLru(ProcessRecord... apps) {
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
+        lru.clear();
+        Collections.addAll(lru, apps);
+    }
+
+    /**
+     * Run updateOomAdjLocked().
+     * - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int).
+     * - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int).
+     */
+    private void updateOomAdj(ProcessRecord... apps) {
+        if (apps.length == 1) {
+            sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE);
+        } else {
+            setProcessesToLru(apps);
+            sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mProcessList.getLruProcessesLOSP().clear();
+        }
+    }
+
     @SuppressWarnings("GuardedBy")
     @Test
     public void testUpdateOomAdj_DoOne_Persistent_TopUi_Sleeping() {
@@ -233,7 +259,7 @@
         app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         app.mState.setHasTopUi(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERSISTENT_PROC_ADJ,
@@ -248,7 +274,7 @@
         app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         app.mState.setHasTopUi(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_PERSISTENT_UI, PERSISTENT_PROC_ADJ,
                 SCHED_GROUP_TOP_APP);
@@ -262,7 +288,7 @@
         app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         doReturn(app).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
         doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_PERSISTENT_UI, PERSISTENT_PROC_ADJ,
@@ -277,7 +303,7 @@
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
         doReturn(app).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
         doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_TOP, FOREGROUND_APP_ADJ, SCHED_GROUP_TOP_APP);
@@ -291,7 +317,7 @@
         doReturn(PROCESS_STATE_TOP_SLEEPING).when(sService.mAtmInternal).getTopProcessState();
         app.mState.setRunningRemoteAnimation(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
 
         assertProcStates(app, PROCESS_STATE_TOP_SLEEPING, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
@@ -304,7 +330,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         doReturn(mock(ActiveInstrumentation.class)).when(app).getActiveInstrumentation();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
         doCallRealMethod().when(app).getActiveInstrumentation();
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, FOREGROUND_APP_ADJ,
@@ -319,7 +345,7 @@
         doReturn(true).when(sService).isReceivingBroadcastLocked(any(ProcessRecord.class),
                 any(int[].class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
         doReturn(false).when(sService).isReceivingBroadcastLocked(any(ProcessRecord.class),
                 any(int[].class));
 
@@ -333,7 +359,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         app.mServices.startExecutingService(mock(ServiceRecord.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_SERVICE, FOREGROUND_APP_ADJ, SCHED_GROUP_BACKGROUND);
     }
@@ -346,7 +372,7 @@
         doReturn(PROCESS_STATE_TOP_SLEEPING).when(sService.mAtmInternal).getTopProcessState();
         doReturn(app).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
         doReturn(null).when(sService).getTopApp();
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -363,7 +389,7 @@
         app.mState.setCurRawAdj(CACHED_APP_MIN_ADJ);
         doReturn(null).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ,
                 SCHED_GROUP_BACKGROUND);
@@ -389,7 +415,7 @@
         })).when(wpc).computeOomAdjFromActivities(
                 any(WindowProcessController.ComputeOomAdjCallback.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
     }
@@ -403,7 +429,7 @@
         doReturn(true).when(wpc).hasRecentTasks();
         app.mState.setLastTopTime(SystemClock.uptimeMillis());
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
         doCallRealMethod().when(wpc).hasRecentTasks();
 
         assertEquals(PROCESS_STATE_CACHED_RECENT, app.mState.getSetProcState());
@@ -417,7 +443,7 @@
         app.mServices.setHasForegroundServices(true, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION,
                 /* hasNoneType=*/false);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -430,7 +456,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -458,7 +484,7 @@
             app.mState.setLastTopTime(SystemClock.uptimeMillis());
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdj(app);
 
             assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
                     PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -477,7 +503,7 @@
                     - sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdj(app);
 
             assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
                     PERCEPTIBLE_MEDIUM_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -503,7 +529,7 @@
                     - sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdj(app);
 
             // Procstate should be lower than FGS. (It should be SERVICE)
             assertEquals(app.mState.getSetProcState(), PROCESS_STATE_SERVICE);
@@ -520,7 +546,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         app.mState.setHasOverlayUi(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -534,7 +560,7 @@
         app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         app.mState.setLastTopTime(SystemClock.uptimeMillis());
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE,
                 PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ, SCHED_GROUP_DEFAULT);
@@ -558,7 +584,7 @@
             s.getConnections().clear();
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdj(app);
 
             assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
         }
@@ -578,7 +604,7 @@
                     nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdj(app);
 
             assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
         }
@@ -599,7 +625,7 @@
             s.getConnections().clear();
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdj(app);
 
             assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
         }
@@ -611,7 +637,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         app.mState.setForcingToImportant(new Object());
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -625,7 +651,7 @@
         WindowProcessController wpc = app.getWindowProcessController();
         doReturn(true).when(wpc).isHeavyWeightProcess();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
         doReturn(false).when(wpc).isHeavyWeightProcess();
 
         assertProcStates(app, PROCESS_STATE_HEAVY_WEIGHT, HEAVY_WEIGHT_APP_ADJ,
@@ -640,7 +666,7 @@
         WindowProcessController wpc = app.getWindowProcessController();
         doReturn(true).when(wpc).isHomeProcess();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_HOME, HOME_APP_ADJ, SCHED_GROUP_BACKGROUND);
     }
@@ -654,7 +680,7 @@
         doReturn(true).when(wpc).isPreviousProcess();
         doReturn(true).when(wpc).hasActivities();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
                 SCHED_GROUP_BACKGROUND);
@@ -669,7 +695,7 @@
         backupTarget.app = app;
         doReturn(backupTarget).when(sService.mBackupTargets).get(anyInt());
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
         doReturn(null).when(sService.mBackupTargets).get(anyInt());
 
         assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, BACKUP_APP_ADJ,
@@ -683,7 +709,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         app.mServices.setHasClientActivities(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertEquals(PROCESS_STATE_CACHED_ACTIVITY_CLIENT, app.mState.getSetProcState());
     }
@@ -695,7 +721,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         app.mServices.setTreatLikeActivity(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
     }
@@ -712,7 +738,7 @@
         s.lastActivity = SystemClock.uptimeMillis();
         app.mServices.startService(s);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_B_ADJ, SCHED_GROUP_BACKGROUND);
     }
@@ -724,7 +750,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         app.mState.setMaxAdj(PERCEPTIBLE_LOW_APP_ADJ);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, PERCEPTIBLE_LOW_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -739,7 +765,7 @@
         app.mState.setCurRawAdj(SERVICE_ADJ);
         doReturn(null).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertTrue(ProcessList.CACHED_APP_MIN_ADJ <= app.mState.getSetAdj());
         assertTrue(ProcessList.CACHED_APP_MAX_ADJ >= app.mState.getSetAdj());
@@ -756,7 +782,7 @@
         s.lastActivity = SystemClock.uptimeMillis();
         app.mServices.startService(s);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
     }
@@ -774,7 +800,7 @@
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
         doReturn(client).when(sService).getTopApp();
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
         doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_SERVICE, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
@@ -790,7 +816,7 @@
         bindService(app, client, null, Context.BIND_WAIVE_PRIORITY
                 | Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
     }
@@ -810,7 +836,7 @@
                 mock(ActivityServiceConnectionsHolder.class));
         doReturn(true).when(cr.activity).isActivityVisible();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
         assertEquals(SCHED_GROUP_TOP_APP_BOUND, app.mState.getSetSchedGroup());
@@ -822,11 +848,8 @@
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         bindService(app, app, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
     }
@@ -841,7 +864,7 @@
         client.mServices.setTreatLikeActivity(true);
         bindService(app, client, null, 0, mock(IBinder.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertEquals(PROCESS_STATE_CACHED_EMPTY, app.mState.getSetProcState());
     }
@@ -861,7 +884,7 @@
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
         doReturn(client).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
         doReturn(null).when(sService).getTopApp();
 
         assertEquals(PREVIOUS_APP_ADJ, app.mState.getSetAdj());
@@ -878,7 +901,7 @@
         client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         client.mState.setHasTopUi(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -894,7 +917,7 @@
         bindService(app, client, null, Context.BIND_IMPORTANT, mock(IBinder.class));
         client.mServices.startExecutingService(mock(ServiceRecord.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
     }
@@ -910,7 +933,7 @@
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
         doReturn(client).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
         doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_DEFAULT);
@@ -926,9 +949,10 @@
         bindService(app, client, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
         client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertEquals(PROCESS_STATE_BOUND_FOREGROUND_SERVICE, app.mState.getSetProcState());
+        assertEquals(PROCESS_STATE_PERSISTENT, client.mState.getSetProcState());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -941,7 +965,7 @@
         bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class));
         client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState());
     }
@@ -956,7 +980,7 @@
         bindService(app, client, null, 0, mock(IBinder.class));
         client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, app.mState.getSetProcState());
     }
@@ -973,13 +997,14 @@
         backupTarget.app = client;
         doReturn(backupTarget).when(sService.mBackupTargets).get(anyInt());
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
+
         doReturn(null).when(sService.mBackupTargets).get(anyInt());
 
         assertEquals(BACKUP_APP_ADJ, app.mState.getSetAdj());
 
         client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj());
     }
@@ -994,7 +1019,7 @@
         bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
         client.mState.setRunningRemoteAnimation(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj());
     }
@@ -1009,7 +1034,7 @@
         bindService(app, client, null, Context.BIND_NOT_VISIBLE, mock(IBinder.class));
         client.mState.setRunningRemoteAnimation(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
     }
@@ -1024,7 +1049,7 @@
         bindService(app, client, null, 0, mock(IBinder.class));
         client.mState.setHasOverlayUi(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
     }
@@ -1040,7 +1065,7 @@
             bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdj(client, app);
 
             assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
         }
@@ -1055,7 +1080,7 @@
             bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdj(client, app);
             doReturn(false).when(wpc).isHeavyWeightProcess();
 
             assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
@@ -1072,7 +1097,7 @@
         bindService(app, client, null, 0, mock(IBinder.class));
         client.mState.setRunningRemoteAnimation(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertEquals(VISIBLE_APP_ADJ, app.mState.getSetAdj());
     }
@@ -1087,7 +1112,7 @@
         bindService(app, client, null, Context.BIND_IMPORTANT_BACKGROUND, mock(IBinder.class));
         client.mState.setHasOverlayUi(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertEquals(PROCESS_STATE_IMPORTANT_BACKGROUND, app.mState.getSetProcState());
     }
@@ -1098,7 +1123,7 @@
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         bindProvider(app, app, null, null, false);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
     }
@@ -1112,12 +1137,8 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindProvider(app, client, null, null, false);
         client.mServices.setTreatLikeActivity(true);
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app, client);
 
         assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
     }
@@ -1133,7 +1154,7 @@
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
         doReturn(client).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
         doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_BOUND_TOP, FOREGROUND_APP_ADJ, SCHED_GROUP_DEFAULT);
@@ -1149,7 +1170,7 @@
         client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindProvider(app, client, null, null, false);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1164,7 +1185,7 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindProvider(app, client, null, null, true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, app);
 
         assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, FOREGROUND_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1177,7 +1198,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         app.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
                 SCHED_GROUP_BACKGROUND);
@@ -1197,7 +1218,7 @@
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
         doReturn(client2).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, client2, app);
         doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
@@ -1217,7 +1238,7 @@
         bindService(app, client2, null, 0, mock(IBinder.class));
         client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, client2, app);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1236,7 +1257,7 @@
         bindService(client, client2, null, 0, mock(IBinder.class));
         client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, client2, app);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1255,13 +1276,12 @@
         bindService(client, client2, null, 0, mock(IBinder.class));
         client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(client2, app, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
-        lru.add(client2);
+
+        // Note: We add processes to LRU but still call updateOomAdjLocked() with a specific
+        // processes.
+        setProcessesToLru(app, client, client2);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1292,13 +1312,8 @@
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client2, client, null, 0, mock(IBinder.class));
         client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
-        lru.add(client2);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app, client, client2);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1321,13 +1336,8 @@
         bindService(client, client2, null, 0, mock(IBinder.class));
         bindService(client2, client, null, 0, mock(IBinder.class));
         client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
-        lru.add(client2);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app, client, client2);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1357,15 +1367,8 @@
         bindService(client3, client4, null, 0, mock(IBinder.class));
         bindService(client4, client3, null, 0, mock(IBinder.class));
         client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
-        lru.add(client2);
-        lru.add(client3);
-        lru.add(client4);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app, client, client2, client3, client4);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1396,14 +1399,8 @@
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
         client3.mState.setForcingToImportant(new Object());
         bindService(app, client3, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
-        lru.add(client2);
-        lru.add(client3);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app, client, client2, client3);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1427,14 +1424,8 @@
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
         client3.mState.setForcingToImportant(new Object());
         bindService(app, client3, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
-        lru.add(client2);
-        lru.add(client3);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app, client, client2, client3);
 
         assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1460,15 +1451,8 @@
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
         client4.mState.setForcingToImportant(new Object());
         bindService(app, client4, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
-        lru.add(client2);
-        lru.add(client3);
-        lru.add(client4);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app, client, client2, client3, client4);
 
         assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1496,15 +1480,8 @@
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
         client4.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app, client4, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
-        lru.add(client2);
-        lru.add(client3);
-        lru.add(client4);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app, client, client2, client3, client4);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1529,7 +1506,7 @@
         client3.mState.setForcingToImportant(new Object());
         bindService(app, client3, null, 0, mock(IBinder.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, client2, client3, app);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1548,7 +1525,7 @@
         bindProvider(client, client2, null, null, false);
         client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, client2, app);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1567,13 +1544,8 @@
         bindProvider(client, client2, null, null, false);
         client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(client2, app, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
-        lru.add(client2);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app, client, client2);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1592,7 +1564,7 @@
         bindProvider(client, client2, null, null, false);
         client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client, client2, app);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1611,14 +1583,8 @@
         bindProvider(client, client2, null, null, false);
         client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindProvider(client2, app, null, null, false);
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
-        lru.add(client2);
-
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app, client, client2);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1643,8 +1609,7 @@
         client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
 
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client1, client2, app1, app2);
 
         assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1653,8 +1618,7 @@
 
         bindService(app1, client1, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
         bindService(app2, client2, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
-        sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app1, app2);
 
         assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
                 SCHED_GROUP_TOP_APP);
@@ -1662,8 +1626,7 @@
                 SCHED_GROUP_DEFAULT);
 
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
-        sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client1, client2, app1, app2);
         assertProcStates(app1, PROCESS_STATE_IMPORTANT_FOREGROUND, VISIBLE_APP_ADJ,
                 SCHED_GROUP_TOP_APP);
         assertProcStates(app2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1689,8 +1652,7 @@
         final ServiceRecord s2 = bindService(app2, client2, null,
                 Context.BIND_IMPORTANT, mock(IBinder.class));
 
-        sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client1, client2, app1, app2);
 
         assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1699,7 +1661,7 @@
 
         bindService(app2, client1, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
                 mock(IBinder.class));
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app2);
         assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ,
                 SCHED_GROUP_DEFAULT);
 
@@ -1715,10 +1677,10 @@
         bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
                 mock(IBinder.class));
 
-        sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app1, app2);
 
-        assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+        // VISIBLE_APP_ADJ is the max oom-adj for BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE.
+        assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
         assertProcStates(app2, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1747,7 +1709,7 @@
 
         bindService(app1, client1, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
 
-        sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client1, app1);
 
         assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1767,7 +1729,7 @@
 
         bindService(app1, client1, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
 
-        sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(client1, app1);
 
         assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1798,13 +1760,7 @@
 
         client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         client2.mState.setForcingToImportant(new Object());
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app1);
-        lru.add(app2);
-        lru.add(app3);
-        lru.add(client1);
-        lru.add(client2);
+        setProcessesToLru(app1, app2, app3, client1, client2);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
         final ComponentName cn1 = ComponentName.unflattenFromString(
@@ -1891,13 +1847,9 @@
         ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(app2);
+
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
-        lru.clear();
+        updateOomAdj(app, app2);
 
         assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1915,13 +1867,8 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app, app2, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(app2);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
-        lru.clear();
+        updateOomAdj(app, app2);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1942,14 +1889,8 @@
         bindService(app2, app3, null, 0, mock(IBinder.class));
         app3.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app3, app, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(app2);
-        lru.add(app3);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
-        lru.clear();
+        updateOomAdj(app, app2, app3);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1990,16 +1931,8 @@
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
         app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app, app5, s, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(app2);
-        lru.add(app3);
-        lru.add(app4);
-        lru.add(app5);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
-        lru.clear();
+        updateOomAdj(app, app2, app3, app4, app5);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -2035,16 +1968,8 @@
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
         app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app, app5, s, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app5);
-        lru.add(app4);
-        lru.add(app3);
-        lru.add(app2);
-        lru.add(app);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
-        lru.clear();
+        updateOomAdj(app5, app4, app3, app2, app);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -2080,16 +2005,8 @@
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
         app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindService(app, app5, s, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app3);
-        lru.add(app4);
-        lru.add(app2);
-        lru.add(app);
-        lru.add(app5);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
-        lru.clear();
+        updateOomAdj(app3, app4, app2, app, app5);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -2119,18 +2036,13 @@
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
         client3.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         bindService(app, client3, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(client);
-        lru.add(client2);
-        lru.add(client3);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdj(app, client, client2, client3);
 
-        assertEquals(PROCESS_CAPABILITY_ALL, client.mState.getSetCapability());
-        assertEquals(PROCESS_CAPABILITY_ALL, client2.mState.getSetCapability());
-        assertEquals(PROCESS_CAPABILITY_ALL, app.mState.getSetCapability());
+        final int expected = PROCESS_CAPABILITY_ALL;
+        assertEquals(expected, client.mState.getSetCapability());
+        assertEquals(expected, client2.mState.getSetCapability());
+        assertEquals(expected, app.mState.getSetCapability());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -2155,16 +2067,8 @@
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
         app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
         bindProvider(app, app5, cr, null, false);
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app);
-        lru.add(app2);
-        lru.add(app3);
-        lru.add(app4);
-        lru.add(app5);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
-        lru.clear();
+        updateOomAdj(app, app2, app3, app4, app5);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -2202,15 +2106,9 @@
         doCallRealMethod().when(s).getConnections();
         s.startRequested = true;
         s.lastActivity = now;
-        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
-        lru.clear();
-        lru.add(app3);
-        lru.add(app2);
-        lru.add(app);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.mNumServiceProcs = 3;
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
-        lru.clear();
+        updateOomAdj(app3, app2, app);
 
         assertEquals(SERVICE_B_ADJ, app3.mState.getSetAdj());
         assertEquals(SERVICE_ADJ, app2.mState.getSetAdj());
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
index 01ce696..8979585 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
@@ -20,6 +20,8 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
 import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
 import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
@@ -73,8 +75,8 @@
         assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         // Make sure another user cannot be started on default display
-        int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, visibleBgUserId,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+        int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, otherUserId, BG_VISIBLE,
+                DEFAULT_DISPLAY);
         assertStartUserResult(result2, USER_ASSIGNMENT_RESULT_FAILURE,
                 "when user (%d) is starting on default display after it was started by user %d",
                 otherUserId, visibleBgUserId);
@@ -117,8 +119,8 @@
         assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         // Make sure another user cannot be started on default display
-        int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, visibleBgUserId,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+        int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, otherUserId, BG_VISIBLE,
+                DEFAULT_DISPLAY);
         assertStartUserResult(result2, USER_ASSIGNMENT_RESULT_FAILURE,
                 "when user (%d) is starting on default display after it was started by user %d",
                 otherUserId, visibleBgUserId);
@@ -126,4 +128,102 @@
 
         listener.verify();
     }
+
+    @Test
+    public void
+       testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnBgOnSecondaryDisplay()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+        startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
+
+        assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public void
+        testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnBgOnDefaultDisplay()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onVisible(PARENT_USER_ID),
+                onVisible(PROFILE_USER_ID));
+        startUserInSecondaryDisplay(PARENT_USER_ID, DEFAULT_DISPLAY);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        // Assert parent user visibility
+        expectUserIsVisible(PARENT_USER_ID);
+        expectUserIsVisibleOnDisplay(PARENT_USER_ID, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(PARENT_USER_ID, INVALID_DISPLAY);
+        expectDisplayAssignedToUser(PARENT_USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+        assertUserCanBeAssignedExtraDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+        // Assert profile user visibility
+        expectUserIsVisible(PROFILE_USER_ID);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+        // Only full user (parent) is assigned to the display
+        expectDisplayAssignedToUser(PROFILE_USER_ID, INVALID_DISPLAY);
+        assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public void testStartBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnBg()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+        startUserInSecondaryDisplay(PARENT_USER_ID, DEFAULT_DISPLAY);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+        // Assert parent user visibility
+        expectUserIsVisible(PARENT_USER_ID);
+        expectUserIsVisibleOnDisplay(PARENT_USER_ID, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(PARENT_USER_ID, INVALID_DISPLAY);
+        expectDisplayAssignedToUser(PARENT_USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+        assertUserCanBeAssignedExtraDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+        // Assert profile user visibility
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+        listener.verify();
+    }
+
+    @Test
+    public void testStartVisibleBgUser_onDefaultDisplay_currentUserId() throws Exception {
+        int currentUserId = INITIAL_CURRENT_USER_ID;
+
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+        int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId,
+                BG_VISIBLE, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE);
+
+        // Assert current user visibility
+        expectUserIsVisible(currentUserId);
+        expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+        expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+
+        assertUserCanBeAssignedExtraDisplay(currentUserId, OTHER_SECONDARY_DISPLAY_ID);
+
+        listener.verify();
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 5176d68..566084a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -44,7 +44,6 @@
 import android.util.IntArray;
 import android.util.Log;
 
-import com.android.internal.util.Preconditions;
 import com.android.server.ExtendedMockitoTestCase;
 
 import org.junit.Before;
@@ -149,6 +148,12 @@
     }
 
     @Test
+    public final void testAssignUserToDisplayOnStart_invalidUserStartMode() {
+        assertThrows(IllegalArgumentException.class, () -> mMediator
+                .assignUserToDisplayOnStart(USER_ID, USER_ID, 666, DEFAULT_DISPLAY));
+    }
+
+    @Test
     public final void testStartFgUser_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
@@ -283,7 +288,7 @@
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
                 BG_VISIBLE, DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
         expectNoDisplayAssignedToUser(PROFILE_USER_ID);
@@ -299,14 +304,14 @@
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
                 BG_VISIBLE, DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
 
         expectNoDisplayAssignedToUser(PROFILE_USER_ID);
         expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
 
-        assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
 
         listener.verify();
     }
@@ -332,6 +337,41 @@
     }
 
     @Test
+    public final void testStartBgProfile_onDefaultDisplay_whenParentIsNotStarted()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void testStartBgProfile_onDefaultDisplay_whenParentIsStartedOnBg()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+        startBackgroundUser(PARENT_USER_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
+
+        assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+        listener.verify();
+    }
+
+    @Test
     public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
@@ -485,8 +525,6 @@
      * se.
      */
     protected final void startUserInSecondaryDisplay(@UserIdInt int userId, int displayId) {
-        Preconditions.checkArgument(displayId != INVALID_DISPLAY && displayId != DEFAULT_DISPLAY,
-                "must pass a secondary display, not %d", displayId);
         Log.d(TAG, "startUserInSecondaryDisplay(" + userId + ", " + displayId + ")");
         int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG_VISIBLE, displayId);
         if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
index 49c6a88..f084063 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
@@ -108,34 +108,6 @@
     }
 
     @Test
-    public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
-            throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(
-                onInvisible(INITIAL_CURRENT_USER_ID),
-                onVisible(PARENT_USER_ID),
-                onVisible(PROFILE_USER_ID));
-        startForegroundUser(PARENT_USER_ID);
-
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-        expectUserCannotBeUnassignedFromDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
-
-        expectUserIsVisible(PROFILE_USER_ID);
-        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
-        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
-        expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
-
-        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
-        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
-
-        assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
-        listener.verify();
-    }
-
-    @Test
     public final void testStartFgUser_onInvalidDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
@@ -268,14 +240,83 @@
     }
 
     @Test
+    public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(PARENT_USER_ID),
+                onVisible(PROFILE_USER_ID));
+        startForegroundUser(PARENT_USER_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+        expectUserCannotBeUnassignedFromDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+
+        expectUserIsVisible(PROFILE_USER_ID);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+        assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+        listener.verify();
+    }
+
+    @Test
     public final void
-            testStartVisibleBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay()
-                    throws Exception {
+            testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnAnotherDisplay()
+            throws Exception {
         AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
         startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
                 BG_VISIBLE, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
+
+        assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+        listener.verify();
+    }
+
+    // Not supported - profiles can only be started on default display
+    @Test
+    public final void
+            testStartVisibleBgProfile_onSecondaryDisplay_whenParentIsStartedVisibleOnThatDisplay()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+        startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
+
+        assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void
+            testStartProfile_onDefaultDisplay_whenParentIsStartedVisibleOnSecondaryDisplay()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+        startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+                DEFAULT_DISPLAY);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
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 863dcb6..a02807f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -70,7 +71,6 @@
     WindowMagnificationManagerStub mWindowMagnificationManagerStub;
 
     @Before
-
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mFullScreenMagnificationControllerStub = new FullScreenMagnificationControllerStub(
@@ -433,27 +433,60 @@
                 eq(newConfig), anyBoolean(), anyInt());
     }
 
+    @Test
+    public void setMagnificationConfigWithActivatedFalse_fullScreenEnabled_resetMagnification() {
+        setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setActivated(false).build();
+        mMagnificationProcessor.setMagnificationConfig(TEST_DISPLAY, config, false, SERVICE_ID);
+
+        verify(mMockFullScreenMagnificationController).reset(eq(TEST_DISPLAY), anyBoolean());
+    }
+
+    @Test
+    public void setMagnificationConfigWithActivatedFalse_windowEnabled_disableMagnification() {
+        setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setActivated(false).build();
+        mMagnificationProcessor.setMagnificationConfig(TEST_DISPLAY, config, false, SERVICE_ID);
+
+        verify(mMockWindowMagnificationManager)
+                .disableWindowMagnification(eq(TEST_DISPLAY), anyBoolean());
+    }
+
+    @Test
+    public void setMagnificationConfigWithActivatedFalse_expectedReturnedValue() {
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setActivated(false).build();
+        assertFalse(mMagnificationProcessor.setMagnificationConfig(
+                        TEST_DISPLAY, config, false, SERVICE_ID));
+    }
+
     private void setMagnificationActivated(int displayId, int configMode) {
         setMagnificationActivated(displayId,
-                new MagnificationConfig.Builder().setMode(configMode).build());
+                new MagnificationConfig.Builder()
+                        .setMode(configMode)
+                        .setScale(TEST_SCALE).build());
     }
 
     private void setMagnificationActivated(int displayId, MagnificationConfig config) {
+        if (!config.isActivated()) {
+            fail("setMagnificationActivated method should be called with config activated true");
+        }
+
         when(mMockMagnificationController.isActivated(displayId, config.getMode())).thenReturn(
                 true);
         mMagnificationProcessor.setMagnificationConfig(displayId, config, false, SERVICE_ID);
         if (config.getMode() == MAGNIFICATION_MODE_FULLSCREEN) {
-            when(mMockMagnificationController.isActivated(displayId,
-                    MAGNIFICATION_MODE_WINDOW)).thenReturn(false);
             mFullScreenMagnificationControllerStub.resetAndStubMethods();
             mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(),
                     config.getCenterX(), config.getCenterY(), false, SERVICE_ID);
+            mWindowMagnificationManagerStub.deactivateIfNeed();
         } else if (config.getMode() == MAGNIFICATION_MODE_WINDOW) {
-            when(mMockMagnificationController.isActivated(displayId,
-                    MAGNIFICATION_MODE_FULLSCREEN)).thenReturn(false);
             mWindowMagnificationManagerStub.resetAndStubMethods();
             mMockWindowMagnificationManager.enableWindowMagnification(displayId, config.getScale(),
                     config.getCenterX(), config.getCenterY());
+            mFullScreenMagnificationControllerStub.deactivateIfNeed();
         }
     }
 
@@ -469,6 +502,7 @@
         private float mScale = 1.0f;
         private float mCenterX = 0;
         private float mCenterY = 0;
+        private boolean mIsActivated = false;
         private boolean mIsRegistered = false;
 
         FullScreenMagnificationControllerStub(
@@ -485,7 +519,11 @@
                     TEST_DISPLAY);
             doAnswer(invocation -> mIsRegistered).when(mScreenMagnificationController).isRegistered(
                     TEST_DISPLAY);
+            doAnswer(invocation -> mIsActivated).when(mScreenMagnificationController).isActivated(
+                    TEST_DISPLAY);
+
             Answer enableMagnificationStubAnswer = invocation -> {
+                mIsActivated = true;
                 mScale = invocation.getArgument(1);
                 mCenterX = invocation.getArgument(2);
                 mCenterY = invocation.getArgument(3);
@@ -495,6 +533,13 @@
                     mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(),
                     anyFloat(), anyFloat(), anyBoolean(), eq(SERVICE_ID));
 
+            Answer disableMagnificationStubAnswer = invocation -> {
+                deactivateIfNeed();
+                return true;
+            };
+            doAnswer(disableMagnificationStubAnswer).when(
+                    mScreenMagnificationController).reset(eq(TEST_DISPLAY), anyBoolean());
+
             Answer registerStubAnswer = invocation -> {
                 mIsRegistered = true;
                 return true;
@@ -508,14 +553,19 @@
             };
             doAnswer(unregisterStubAnswer).when(
                     mScreenMagnificationController).unregister(eq(TEST_DISPLAY));
-            doAnswer(unregisterStubAnswer).when(
-                    mScreenMagnificationController).reset(eq(TEST_DISPLAY), anyBoolean());
         }
 
         public void resetAndStubMethods() {
             Mockito.reset(mScreenMagnificationController);
             stubMethods();
         }
+
+        public void deactivateIfNeed() {
+            mScale = 1.0f;
+            mCenterX = 0;
+            mCenterY = 0;
+            mIsActivated = false;
+        }
     }
 
     private static class WindowMagnificationManagerStub {
@@ -523,6 +573,7 @@
         private float mScale = 1.0f;
         private float mCenterX = 0;
         private float mCenterY = 0;
+        private boolean mIsEnabled = false;
 
         WindowMagnificationManagerStub(
                 WindowMagnificationManager windowMagnificationManager) {
@@ -536,7 +587,11 @@
                     TEST_DISPLAY);
             doAnswer(invocation -> mCenterY).when(mWindowMagnificationManager).getCenterY(
                     TEST_DISPLAY);
+            doAnswer(invocation -> mIsEnabled).when(mWindowMagnificationManager)
+                    .isWindowMagnifierEnabled(TEST_DISPLAY);
+
             Answer enableWindowMagnificationStubAnswer = invocation -> {
+                mIsEnabled = true;
                 mScale = invocation.getArgument(1);
                 mCenterX = invocation.getArgument(2);
                 mCenterY = invocation.getArgument(3);
@@ -548,11 +603,29 @@
             doAnswer(enableWindowMagnificationStubAnswer).when(
                     mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY),
                     anyFloat(), anyFloat(), anyFloat(), any(), anyInt());
+
+            Answer disableWindowMagnificationStubAnswer = invocation -> {
+                deactivateIfNeed();
+                return true;
+            };
+            doAnswer(disableWindowMagnificationStubAnswer).when(
+                    mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY),
+                    anyBoolean());
+            doAnswer(disableWindowMagnificationStubAnswer).when(
+                    mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY),
+                    anyBoolean(), any());
         }
 
         public void resetAndStubMethods() {
             Mockito.reset(mWindowMagnificationManager);
             stubMethods();
         }
+
+        public void deactivateIfNeed() {
+            mScale = 1.0f;
+            mCenterX = Float.NaN;
+            mCenterY = Float.NaN;
+            mIsEnabled = false;
+        }
     }
 }
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 231b2f32..407c575 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
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 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;
@@ -404,6 +405,22 @@
     }
 
     @Test
+    public void
+            configTransitionToWindowModeAndActivatedFalse_fullScreenMagnifying_doNotEnableWindow()
+            throws RemoteException {
+        activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+
+        MagnificationConfig config = (new MagnificationConfig.Builder())
+                .setMode(MODE_WINDOW).setActivated(false).build();
+        mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+                config, false, TEST_SERVICE_ID);
+
+        verify(mMockConnection.getConnection(), never()).enableWindowMagnification(anyInt(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(),
+                nullable(IRemoteMagnificationAnimationCallback.class));
+    }
+
+    @Test
     public void configTransitionToFullScreen_windowMagnifying_disableWindowAndEnableFullScreen()
             throws RemoteException {
         final boolean animate = true;
@@ -420,6 +437,49 @@
     }
 
     @Test
+    public void
+            configTransitionToFullScreenAndActivatedFalse_windowMagnifying_doNotEnableFullScreen()
+            throws RemoteException {
+        final boolean animate = true;
+        activateMagnifier(MODE_WINDOW, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+        MagnificationConfig config = (new MagnificationConfig.Builder())
+                .setMode(MODE_FULLSCREEN).setActivated(false).build();
+        mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+                config, animate, TEST_SERVICE_ID);
+        mMockConnection.invokeCallbacks();
+
+        verify(mScreenMagnificationController, never()).setScaleAndCenter(anyInt(),
+                anyFloat(), anyFloat(), anyFloat(),
+                anyBoolean(), anyInt());
+    }
+
+    @Test
+    public void configTransitionToActivatedFalse_fullScreenMagnifying_disableFullScreen()
+            throws RemoteException {
+        activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+        MagnificationConfig config = (new MagnificationConfig.Builder())
+                .setMode(MODE_FULLSCREEN).setActivated(false).build();
+        mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+                config, false, TEST_SERVICE_ID);
+
+        verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), eq(false));
+    }
+
+    @Test
+    public void configTransitionToActivatedFalse_windowMagnifying_disableWindow()
+            throws RemoteException {
+        final boolean animate = true;
+        activateMagnifier(MODE_WINDOW, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+        MagnificationConfig config = (new MagnificationConfig.Builder())
+                .setMode(MODE_WINDOW).setActivated(false).build();
+        mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+                config, animate, TEST_SERVICE_ID);
+
+        verify(mMockConnection.getConnection()).disableWindowMagnification(anyInt(),
+                nullable(IRemoteMagnificationAnimationCallback.class));
+    }
+
+    @Test
     public void configTransitionToFullScreen_userSettingsDisablingFullScreen_enableFullScreen()
             throws RemoteException {
         activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 82f6493..c952609 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -945,13 +945,15 @@
             }
 
             mListener = listener;
-            mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates);
+            mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates,
+                    SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
             mListener.onStateChanged(mSupportedDeviceStates[0].getIdentifier());
         }
 
         public void notifySupportedDeviceStates(DeviceState[] supportedDeviceStates) {
             mSupportedDeviceStates = supportedDeviceStates;
-            mListener.onSupportedDeviceStatesChanged(supportedDeviceStates);
+            mListener.onSupportedDeviceStatesChanged(supportedDeviceStates,
+                    SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
         }
 
         public void setState(int identifier) {
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
new file mode 100644
index 0000000..8196d6a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
@@ -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.server.devicestate;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+/**
+ * Unit tests for {@link DeviceStateNotificationController}.
+ * <p/>
+ * Run with <code>atest com.android.server.devicestate.DeviceStateNotificationControllerTest</code>.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DeviceStateNotificationControllerTest {
+
+    private static final int STATE_WITHOUT_NOTIFICATION = 1;
+    private static final int STATE_WITH_ACTIVE_NOTIFICATION = 2;
+    private static final int STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION = 3;
+
+    private static final int VALID_APP_UID = 1000;
+    private static final int INVALID_APP_UID = 2000;
+    private static final String VALID_APP_NAME = "Valid app name";
+    private static final String INVALID_APP_NAME = "Invalid app name";
+    private static final String VALID_APP_LABEL = "Valid app label";
+
+    private static final String NAME_1 = "name1";
+    private static final String TITLE_1 = "title1";
+    private static final String CONTENT_1 = "content1:%1$s";
+    private static final String NAME_2 = "name2";
+    private static final String TITLE_2 = "title2";
+    private static final String CONTENT_2 = "content2:%1$s";
+    private static final String THERMAL_TITLE_2 = "thermal_title2";
+    private static final String THERMAL_CONTENT_2 = "thermal_content2";
+
+    private DeviceStateNotificationController mController;
+
+    private final ArgumentCaptor<Notification> mNotificationCaptor = ArgumentCaptor.forClass(
+            Notification.class);
+    private final NotificationManager mNotificationManager = mock(NotificationManager.class);
+
+    @Before
+    public void setup() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        Handler handler = mock(Handler.class);
+        PackageManager packageManager = mock(PackageManager.class);
+        Runnable cancelStateRunnable = mock(Runnable.class);
+        ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
+
+        final SparseArray<DeviceStateNotificationController.NotificationInfo> notificationInfos =
+                new SparseArray<>();
+        notificationInfos.put(STATE_WITH_ACTIVE_NOTIFICATION,
+                new DeviceStateNotificationController.NotificationInfo(
+                        NAME_1, TITLE_1, CONTENT_1,
+                        "", ""));
+        notificationInfos.put(STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION,
+                new DeviceStateNotificationController.NotificationInfo(
+                        NAME_2, TITLE_2, CONTENT_2,
+                        THERMAL_TITLE_2, THERMAL_CONTENT_2));
+
+        when(packageManager.getNameForUid(VALID_APP_UID)).thenReturn(VALID_APP_NAME);
+        when(packageManager.getNameForUid(INVALID_APP_UID)).thenReturn(INVALID_APP_NAME);
+        when(packageManager.getApplicationInfo(eq(VALID_APP_NAME), ArgumentMatchers.any()))
+                .thenReturn(applicationInfo);
+        when(packageManager.getApplicationInfo(eq(INVALID_APP_NAME), ArgumentMatchers.any()))
+                .thenThrow(new PackageManager.NameNotFoundException());
+        when(applicationInfo.loadLabel(eq(packageManager))).thenReturn(VALID_APP_LABEL);
+
+        mController = new DeviceStateNotificationController(
+                context, handler, cancelStateRunnable, notificationInfos,
+                packageManager, mNotificationManager);
+    }
+
+    @Test
+    public void test_activeNotification() {
+        mController.showStateActiveNotificationIfNeeded(
+                STATE_WITH_ACTIVE_NOTIFICATION, VALID_APP_UID);
+
+        // Verify that the notification manager is called with correct notification information.
+        verify(mNotificationManager).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+        Notification notification = mNotificationCaptor.getValue();
+        assertEquals(TITLE_1, notification.extras.getString(Notification.EXTRA_TITLE));
+        assertEquals(String.format(CONTENT_1, VALID_APP_LABEL),
+                notification.extras.getString(Notification.EXTRA_TEXT));
+        assertEquals(Notification.FLAG_ONGOING_EVENT,
+                notification.flags & Notification.FLAG_ONGOING_EVENT);
+
+        // Verify that the notification action is as expected.
+        Notification.Action[] actions = notification.actions;
+        assertEquals(1, actions.length);
+        Notification.Action action = actions[0];
+        assertEquals(DeviceStateNotificationController.INTENT_ACTION_CANCEL_STATE,
+                action.actionIntent.getIntent().getAction());
+
+        // Verify that the notification is properly canceled.
+        mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
+        verify(mNotificationManager).cancel(
+                DeviceStateNotificationController.NOTIFICATION_TAG,
+                DeviceStateNotificationController.NOTIFICATION_ID);
+    }
+
+    @Test
+    public void test_thermalNotification() {
+        // Verify that the active notification is created.
+        mController.showStateActiveNotificationIfNeeded(
+                STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION, VALID_APP_UID);
+        verify(mNotificationManager).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+        Notification notification = mNotificationCaptor.getValue();
+        assertEquals(TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+        assertEquals(String.format(CONTENT_2, VALID_APP_LABEL),
+                notification.extras.getString(Notification.EXTRA_TEXT));
+        assertEquals(Notification.FLAG_ONGOING_EVENT,
+                notification.flags & Notification.FLAG_ONGOING_EVENT);
+        Mockito.clearInvocations(mNotificationManager);
+
+        // Verify that the thermal critical notification is created.
+        mController.showThermalCriticalNotificationIfNeeded(
+                STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION);
+        verify(mNotificationManager).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+        notification = mNotificationCaptor.getValue();
+        assertEquals(THERMAL_TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+        assertEquals(THERMAL_CONTENT_2, notification.extras.getString(Notification.EXTRA_TEXT));
+        assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT);
+
+        // Verify that the notification is canceled.
+        mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
+        verify(mNotificationManager).cancel(
+                DeviceStateNotificationController.NOTIFICATION_TAG,
+                DeviceStateNotificationController.NOTIFICATION_ID);
+    }
+
+    @Test
+    public void test_deviceStateWithoutNotification() {
+        // Verify that no notification is created.
+        mController.showStateActiveNotificationIfNeeded(
+                STATE_WITHOUT_NOTIFICATION, VALID_APP_UID);
+        verify(mNotificationManager, Mockito.never()).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index 430504c..fe37f42 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -58,7 +58,7 @@
 
     @Test
     public void addRequest() {
-        OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(request));
 
@@ -68,14 +68,14 @@
 
     @Test
     public void addRequest_cancelExistingRequestThroughNewRequest() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(firstRequest));
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
-        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(secondRequest));
 
@@ -86,7 +86,7 @@
 
     @Test
     public void addRequest_cancelActiveRequest() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         mController.addRequest(firstRequest);
@@ -100,7 +100,7 @@
 
     @Test
     public void addBaseStateRequest() {
-        OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
         assertNull(mStatusListener.getLastStatus(request));
 
@@ -110,14 +110,14 @@
 
     @Test
     public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
         assertNull(mStatusListener.getLastStatus(firstRequest));
 
         mController.addBaseStateRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
-        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
         assertNull(mStatusListener.getLastStatus(secondRequest));
 
@@ -128,7 +128,7 @@
 
     @Test
     public void addBaseStateRequest_cancelActiveBaseStateRequest() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addBaseStateRequest(firstRequest);
@@ -142,12 +142,13 @@
 
     @Test
     public void handleBaseStateChanged() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */,
                 DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */,
                 OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* uid */,
                 0 /* requestedState */,
                 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
@@ -167,10 +168,11 @@
 
     @Test
     public void handleProcessDied() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* uid */,
                 1 /* requestedState */,
                 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
@@ -189,10 +191,11 @@
     public void handleProcessDied_stickyRequests() {
         mController.setStickyRequestsAllowed(true);
 
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* uid */,
                 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
@@ -211,10 +214,11 @@
 
     @Test
     public void handleNewSupportedStates() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* uid */,
                 1 /* requestedState */,
                 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
@@ -224,18 +228,20 @@
         mController.addBaseStateRequest(baseStateRequest);
         assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
 
-        mController.handleNewSupportedStates(new int[]{0, 1});
+        mController.handleNewSupportedStates(new int[]{0, 1},
+                DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
         assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
 
-        mController.handleNewSupportedStates(new int[]{0});
+        mController.handleNewSupportedStates(new int[]{0},
+                DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
         assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
     public void cancelOverrideRequestsTest() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         mController.addRequest(firstRequest);
@@ -250,7 +256,7 @@
         private Map<OverrideRequest, Integer> mLastStatusMap = new HashMap<>();
 
         @Override
-        public void onStatusChanged(@NonNull OverrideRequest request, int newStatus) {
+        public void onStatusChanged(@NonNull OverrideRequest request, int newStatus, int flags) {
             mLastStatusMap.put(request, newStatus);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 2d252cb..9a43762 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -157,7 +157,7 @@
        VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
            return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
-                   (String name, boolean secure) -> mMockDisplayToken);
+                   (String name, boolean secure, float refreshRate) -> mMockDisplayToken);
        }
 
        @Override
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 3a3a507..a35f4ea 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -754,7 +754,7 @@
 
     @Test
     public void testVotingWithSwitchingTypeRenderFrameRateOnlyRenderRateIsNotPhysicalRefreshRate() {
-        DisplayModeDirector director = createDirectorFromFpsRange(90, 120);
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
         SparseArray<Vote> votes = new SparseArray<>();
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
         votesByDisplay.put(DISPLAY_ID, votes);
@@ -775,23 +775,23 @@
         assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
         assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
         assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
 
         director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
         assertThat(director.getModeSwitchingType())
                 .isEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
 
         desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
         assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
         assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
 
-        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
     }
 
     @Test
@@ -1732,6 +1732,31 @@
     }
 
     @Test
+    public void testRenderFrameRateIsAchievableWhenSwitchingTypeNone() {
+        Display.Mode[] modes = new Display.Mode[2];
+        modes[0] = new Display.Mode(
+                /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
+        modes[1] = new Display.Mode(
+                /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
+
+        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[1]);
+        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
+
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+                Vote.forRenderFrameRates(0, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+    }
+
+    @Test
     public void testProximitySensorVoting() {
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[]{60.f, 90.f}, 0);
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 7fac9b6..7125796 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -19,6 +19,9 @@
 
 import static android.content.Context.SENSOR_SERVICE;
 
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL;
 import static com.android.server.policy.DeviceStateProviderImpl.DEFAULT_DEVICE_STATE;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -124,7 +127,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(new DeviceState[]{DEFAULT_DEVICE_STATE},
                 mDeviceStateArrayCaptor.getValue());
 
@@ -151,7 +155,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
                 new DeviceState(1, "", 0 /* flags */),
                 new DeviceState(2, "", 0 /* flags */) };
@@ -183,7 +188,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
                 new DeviceState(1, "", DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS),
                 new DeviceState(2, "", 0 /* flags */) };
@@ -212,7 +218,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
                 new DeviceState(1, "", 0 /* flags */),
                 new DeviceState(2, "", 0 /* flags */) };
@@ -247,7 +254,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
                 new DeviceState(1, "", 0 /* flags */),
                 new DeviceState(2, "CLOSED", 0 /* flags */) };
@@ -265,7 +273,8 @@
         Mockito.clearInvocations(listener);
 
         provider.notifyLidSwitchChanged(1, true /* lidOpen */);
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(1, mIntegerCaptor.getValue().intValue());
     }
@@ -336,7 +345,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
                         new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -358,7 +368,8 @@
 
         provider.onSensorChanged(event0);
 
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(3, mIntegerCaptor.getValue().intValue());
 
@@ -370,7 +381,8 @@
 
         provider.onSensorChanged(event1);
 
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(2, mIntegerCaptor.getValue().intValue());
 
@@ -382,7 +394,8 @@
 
         provider.onSensorChanged(event2);
 
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(1, mIntegerCaptor.getValue().intValue());
     }
@@ -397,7 +410,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
                         new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -410,12 +424,14 @@
         Mockito.clearInvocations(listener);
 
         provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_MODERATE);
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         Mockito.clearInvocations(listener);
 
         // The THERMAL_TEST state should be disabled.
         provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_CRITICAL);
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL));
         assertArrayEquals(
                 new DeviceState[]{
                         new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -426,7 +442,8 @@
 
         // The THERMAL_TEST state should be re-enabled.
         provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL));
         assertArrayEquals(
                 new DeviceState[]{
                         new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -468,7 +485,8 @@
 
         provider.onSensorChanged(event2);
 
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
     }
 
@@ -513,7 +531,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
                         new DeviceState(1, "CLOSED", 0 /* flags */),
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index fc830a9..9facb4b 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -516,7 +516,7 @@
 
         long vibrationId = 1;
         VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
-        Vibration vibration = createVibration(vibrationId, CombinedVibration.createParallel(
+        HalVibration vibration = createVibration(vibrationId, CombinedVibration.createParallel(
                 VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
         vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
         startThreadAndDispatcher(vibration);
@@ -683,7 +683,7 @@
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
                 .compose();
-        Vibration vib = createVibration(vibrationId, CombinedVibration.createParallel(effect));
+        HalVibration vib = createVibration(vibrationId, CombinedVibration.createParallel(effect));
         vib.addFallback(VibrationEffect.EFFECT_TICK, fallback);
         startThreadAndDispatcher(vib);
         waitForCompletion();
@@ -1592,7 +1592,7 @@
         return startThreadAndDispatcher(createVibration(vibrationId, effect));
     }
 
-    private VibrationStepConductor startThreadAndDispatcher(Vibration vib) {
+    private VibrationStepConductor startThreadAndDispatcher(HalVibration vib) {
         mControllers = createVibratorControllers();
         VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
                 mEffectAdapter, mControllers, mManagerHooks);
@@ -1624,8 +1624,8 @@
         mTestLooper.dispatchAll();  // Flush callbacks
     }
 
-    private Vibration createVibration(long id, CombinedVibration effect) {
-        return new Vibration(mVibrationToken, (int) id, effect, ATTRS, UID, DISPLAY_ID,
+    private HalVibration createVibration(long id, CombinedVibration effect) {
+        return new HalVibration(mVibrationToken, (int) id, effect, ATTRS, UID, DISPLAY_ID,
                 PACKAGE_NAME, "reason");
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index c83afb7..f040da8 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2034,7 +2034,7 @@
 
     private void vibrateAndWaitUntilFinished(VibratorManagerService service,
             CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
-        Vibration vib =
+        HalVibration vib =
                 service.vibrateInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, effect, attrs,
                         "some reason", service);
         if (vib != null) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 49edde5..12f124e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -525,6 +525,13 @@
 
     @Test
     public void testZenUpgradeNotification() {
+        /**
+         * Commit a485ec65b5ba947d69158ad90905abf3310655cf disabled DND status change
+         * notification on watches. So, assume that the device is not watch.
+         */
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
+
         // shows zen upgrade notification if stored settings says to shows,
         // zen has not been updated, boot is completed
         // and we're setting zen mode on
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 1ab8c1f..b8d31ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -2332,6 +2332,29 @@
     }
 
     @Test
+    public void testDisplayIgnoreOrientationRequest_disabledViaDeviceConfig_orientationRespected() {
+        // Set up a display in landscape
+        setUpDisplaySizeWithApp(2800, 1400);
+
+        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
+                RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        spyOn(activity.mWmService.mLetterboxConfiguration);
+        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
+                .isIgnoreOrientationRequestAllowed();
+
+        // Display should not be rotated.
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.mDisplayContent.getOrientation());
+
+        doReturn(false).when(activity.mWmService.mLetterboxConfiguration)
+                .isIgnoreOrientationRequestAllowed();
+
+        // Display should be rotated.
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, activity.mDisplayContent.getOrientation());
+    }
+
+    @Test
     public void testSandboxDisplayApis_unresizableAppNotSandboxed() {
         // Set up a display in landscape with an unresizable app.
         setUpDisplaySizeWithApp(2500, 1000);
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index af0eca9..7332d2d 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -20,6 +20,7 @@
     srcs: [":services.voiceinteraction-sources"],
     libs: [
         "services.core",
+        "app-compat-annotations",
         "android.hardware.power-V1-java",
         "android.hardware.power-V1.0-java",
     ],
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 81717f4..7824c69 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -298,35 +298,33 @@
         }
 
         @Override
-        public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
+        public int startRecognition(GenericSoundModel soundModel,
+                IRecognitionStatusCallback callback,
                 RecognitionConfig config, boolean runInBatterySaverMode) {
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
                 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+
+                if (soundModel == null) {
+                    Slog.e(TAG, "Null model passed to startRecognition");
+                    return STATUS_ERROR;
+                }
+
                 if (runInBatterySaverMode) {
                     enforceCallingPermission(Manifest.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER);
                 }
 
                 if (DEBUG) {
-                    Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
+                    Slog.i(TAG, "startRecognition(): Uuid : " + soundModel.toString());
                 }
 
                 sEventLogger.enqueue(new EventLogger.StringEvent(
-                        "startRecognition(): Uuid : " + parcelUuid));
+                        "startRecognition(): Uuid : " + soundModel.getUuid().toString()));
 
-                GenericSoundModel model = getSoundModel(parcelUuid);
-                if (model == null) {
-                    Slog.w(TAG, "Null model in database for id: " + parcelUuid);
-
-                    sEventLogger.enqueue(new EventLogger.StringEvent(
-                            "startRecognition(): Null model in database for id: " + parcelUuid));
-
-                    return STATUS_ERROR;
-                }
-
-                int ret = mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
+                int ret = mSoundTriggerHelper.startGenericRecognition(soundModel.getUuid(),
+                        soundModel,
                         callback, config, runInBatterySaverMode);
                 if (ret == STATUS_OK) {
-                    mSoundModelStatTracker.onStart(parcelUuid.getUuid());
+                    mSoundModelStatTracker.onStart(soundModel.getUuid());
                 }
                 return ret;
             }
@@ -379,8 +377,7 @@
 
                 sEventLogger.enqueue(new EventLogger.StringEvent("updateSoundModel(): model = "
                         + soundModel));
-
-                mDbHelper.updateGenericSoundModel(soundModel);
+               mDbHelper.updateGenericSoundModel(soundModel);
             }
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 445446e..cc22847 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -51,7 +51,6 @@
 import android.os.ServiceManager;
 import android.os.SharedMemory;
 import android.provider.DeviceConfig;
-import android.service.voice.HotwordDetectedResult;
 import android.service.voice.HotwordDetectionService;
 import android.service.voice.HotwordDetector;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
@@ -66,6 +65,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 import com.android.internal.infra.ServiceConnector;
 import com.android.server.LocalServices;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -88,19 +88,18 @@
     static final boolean DEBUG = false;
 
     /**
-     * For apps targeting Android API {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above,
-     * implementors of the {@link HotwordDetectionService} must not augment the phrase IDs which are
-     * supplied via {@link HotwordDetectionService
-     * #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback)}.
+     * For apps targeting Android API Build.VERSION_CODES#UPSIDE_DOWN_CAKE and above,
+     * implementors of the HotwordDetectionService must not augment the phrase IDs which are
+     * supplied via HotwordDetectionService
+     * #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback).
      *
-     * <p>The {@link HotwordDetectedResult#getHotwordPhraseId()} must match one of the phrase IDs
-     * from the {@link android.service.voice.AlwaysOnHotwordDetector
-     * .EventPayload#getKeyphraseRecognitionExtras()} list.
+     * <p>The HotwordDetectedResult#getHotwordPhraseId() must match one of the phrase IDs
+     * from the AlwaysOnHotwordDetector.EventPayload#getKeyphraseRecognitionExtras() list.
      * </p>
      *
-     * <p>This behavior change is made to ensure the {@link HotwordDetectionService} honors what
-     * it receives from the {@link android.hardware.soundtrigger.SoundTriggerModule}, and it
-     * cannot signal to the client application a phrase which was not origially detected.
+     * <p>This behavior change is made to ensure the HotwordDetectionService honors what
+     * it receives from the android.hardware.soundtrigger.SoundTriggerModule, and it
+     * cannot signal to the client application a phrase which was not originally detected.
      * </p>
      */
     @ChangeId
@@ -328,6 +327,15 @@
         session.startListeningFromMicLocked(audioFormat, callback);
     }
 
+    public void setVisualQueryDetectionAttentionListenerLocked(
+            @Nullable IVisualQueryDetectionAttentionListener listener) {
+        final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
+        if (session == null) {
+            return;
+        }
+        session.setVisualQueryDetectionAttentionListenerLocked(listener);
+    }
+
     /**
      * This method is only used by VisualQueryDetector.
      */
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index 621c3de..33150d8 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -33,6 +33,7 @@
 import android.util.Slog;
 
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -49,6 +50,7 @@
 final class VisualQueryDetectorSession extends DetectorSession {
 
     private static final String TAG = "VisualQueryDetectorSession";
+    private IVisualQueryDetectionAttentionListener mAttentionListener;
     private boolean mEgressingData;
     private boolean mQueryStreaming;
 
@@ -64,6 +66,7 @@
                 logging);
         mEgressingData = false;
         mQueryStreaming = false;
+        mAttentionListener = null;
     }
 
     @Override
@@ -74,6 +77,11 @@
         //TODO(b/261783819): Starts detection in VisualQueryDetectionService.
     }
 
+    void setVisualQueryDetectionAttentionListenerLocked(
+            @Nullable IVisualQueryDetectionAttentionListener listener) {
+        mAttentionListener = listener;
+    }
+
     @SuppressWarnings("GuardedBy")
     void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
@@ -86,15 +94,31 @@
             @Override
             public void onAttentionGained() {
                 Slog.v(TAG, "BinderCallback#onAttentionGained");
-                //TODO check to see if there is an active SysUI listener registered
                 mEgressingData = true;
+                if (mAttentionListener == null) {
+                    return;
+                }
+                try {
+                    mAttentionListener.onAttentionGained();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error delivering attention gained event.", e);
+                    return;
+                }
             }
 
             @Override
             public void onAttentionLost() {
                 Slog.v(TAG, "BinderCallback#onAttentionLost");
-                //TODO check to see if there is an active SysUI listener registered
                 mEgressingData = false;
+                if (mAttentionListener == null) {
+                    return;
+                }
+                try {
+                    mAttentionListener.onAttentionLost();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error delivering attention lost event.", e);
+                    return;
+                }
             }
 
             @Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 38bf9c2..0abed0b 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -87,6 +87,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
 import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -1338,6 +1339,39 @@
             }
         }
 
+        @android.annotation.EnforcePermission(
+                android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+        @Override
+        public void enableVisualQueryDetection(
+                IVisualQueryDetectionAttentionListener listener) {
+            super.enableVisualQueryDetection_enforcePermission();
+            synchronized (this) {
+
+                if (mImpl == null) {
+                    Slog.w(TAG,
+                            "enableVisualQueryDetection without running voice interaction service");
+                    return;
+                }
+                this.mImpl.setVisualQueryDetectionAttentionListenerLocked(listener);
+            }
+        }
+
+        @android.annotation.EnforcePermission(
+                android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+        @Override
+        public void disableVisualQueryDetection() {
+            super.disableVisualQueryDetection_enforcePermission();
+            synchronized (this) {
+                if (mImpl == null) {
+                    Slog.w(TAG,
+                            "disableVisualQueryDetection without running voice interaction"
+                                    + " service");
+                    return;
+                }
+                this.mImpl.setVisualQueryDetectionAttentionListenerLocked(null);
+            }
+        }
+
         @Override
         public void startPerceiving(
                 IVisualQueryDetectionVoiceInteractionCallback callback)
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 127195c..3dbd269 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -72,7 +72,9 @@
 import android.util.Slog;
 import android.view.IWindowManager;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.internal.app.IVoiceInteractor;
@@ -247,6 +249,7 @@
                 Context.RECEIVER_EXPORTED);
     }
 
+    @GuardedBy("this")
     public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) {
         final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid);
         final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid);
@@ -256,6 +259,7 @@
                 /* direct= */ true);
     }
 
+    @GuardedBy("this")
     public boolean showSessionLocked(@Nullable Bundle args, int flags,
             @Nullable String attributionTag,
             @Nullable IVoiceInteractionSessionShowCallback showCallback,
@@ -328,6 +332,7 @@
         }
     }
 
+    @GuardedBy("this")
     public boolean hideSessionLocked() {
         if (mActiveSession != null) {
             return mActiveSession.hideLocked();
@@ -335,6 +340,7 @@
         return false;
     }
 
+    @GuardedBy("this")
     public boolean deliverNewSessionLocked(IBinder token,
             IVoiceInteractionSession session, IVoiceInteractor interactor) {
         if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -345,6 +351,7 @@
         return true;
     }
 
+    @GuardedBy("this")
     public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid,
             int callingUid, IBinder token, Intent intent, String resolvedType) {
         try {
@@ -367,6 +374,7 @@
         }
     }
 
+    @GuardedBy("this")
     public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid,
             int callingUid, IBinder token, Intent intent, String resolvedType) {
         try {
@@ -389,6 +397,7 @@
         }
     }
 
+    @GuardedBy("this")
     public void requestDirectActionsLocked(@NonNull IBinder token, int taskId,
             @NonNull IBinder assistToken,  @Nullable RemoteCallback cancellationCallback,
             @NonNull RemoteCallback callback) {
@@ -444,6 +453,7 @@
         }
     }
 
+    @GuardedBy("this")
     void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
             @Nullable Bundle arguments, int taskId, IBinder assistToken,
             @Nullable RemoteCallback cancellationCallback,
@@ -470,6 +480,7 @@
         }
     }
 
+    @GuardedBy("this")
     public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
         try {
             if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -482,6 +493,7 @@
         }
     }
 
+    @GuardedBy("this")
     public void closeSystemDialogsLocked(IBinder token) {
         try {
             if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -494,6 +506,7 @@
         }
     }
 
+    @GuardedBy("this")
     public void finishLocked(IBinder token, boolean finishTask) {
         if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
             Slog.w(TAG, "finish does not match active session");
@@ -503,6 +516,7 @@
         mActiveSession = null;
     }
 
+    @GuardedBy("this")
     public void setDisabledShowContextLocked(int callingUid, int flags) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -512,6 +526,7 @@
         mDisabledShowContext = flags;
     }
 
+    @GuardedBy("this")
     public int getDisabledShowContextLocked(int callingUid) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -521,6 +536,7 @@
         return mDisabledShowContext;
     }
 
+    @GuardedBy("this")
     public int getUserDisabledShowContextLocked(int callingUid) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -534,6 +550,7 @@
         return mInfo.getSupportsLocalInteraction();
     }
 
+    @GuardedBy("this")
     public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
@@ -546,6 +563,7 @@
         mActiveSession.startListeningVisibleActivityChangedLocked();
     }
 
+    @GuardedBy("this")
     public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token);
@@ -558,6 +576,7 @@
         mActiveSession.stopListeningVisibleActivityChangedLocked();
     }
 
+    @GuardedBy("this")
     public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
         if (DEBUG) {
             Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
@@ -572,6 +591,7 @@
         mActiveSession.notifyActivityDestroyedLocked(activityToken);
     }
 
+    @GuardedBy("this")
     public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
         if (DEBUG) {
             Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
@@ -586,6 +606,7 @@
         mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
     }
 
+    @GuardedBy("this")
     public void updateStateLocked(
             @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
@@ -606,6 +627,7 @@
         }
     }
 
+    @GuardedBy("this")
     private void verifyDetectorForHotwordDetectionLocked(
             @Nullable SharedMemory sharedMemory,
             IHotwordRecognitionStatusCallback callback,
@@ -663,6 +685,7 @@
                 voiceInteractionServiceUid);
     }
 
+    @GuardedBy("this")
     private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
         Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
 
@@ -701,6 +724,7 @@
         }
     }
 
+    @GuardedBy("this")
     public void initAndVerifyDetectorLocked(
             @NonNull Identity voiceInteractorIdentity,
             @Nullable PersistableBundle options,
@@ -730,6 +754,7 @@
                 detectorType);
     }
 
+    @GuardedBy("this")
     public void destroyDetectorLocked(IBinder token) {
         Slog.v(TAG, "destroyDetectorLocked");
 
@@ -748,6 +773,7 @@
         }
     }
 
+    @GuardedBy("this")
     public void shutdownHotwordDetectionServiceLocked() {
         if (DEBUG) {
             Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
@@ -760,6 +786,16 @@
         mHotwordDetectionConnection = null;
     }
 
+    @GuardedBy("this")
+    public void setVisualQueryDetectionAttentionListenerLocked(
+            @Nullable IVisualQueryDetectionAttentionListener listener) {
+        if (mHotwordDetectionConnection == null) {
+            return;
+        }
+        mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
+    }
+
+    @GuardedBy("this")
     public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
@@ -773,6 +809,7 @@
         mHotwordDetectionConnection.startPerceivingLocked(callback);
     }
 
+    @GuardedBy("this")
     public void stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
@@ -786,6 +823,7 @@
         mHotwordDetectionConnection.stopPerceivingLocked();
     }
 
+    @GuardedBy("this")
     public void startListeningFromMicLocked(
             AudioFormat audioFormat,
             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
@@ -801,6 +839,7 @@
         mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
     }
 
+    @GuardedBy("this")
     public void startListeningFromExternalSourceLocked(
             ParcelFileDescriptor audioStream,
             AudioFormat audioFormat,
@@ -825,6 +864,7 @@
                 options, token, callback);
     }
 
+    @GuardedBy("this")
     public void stopListeningFromMicLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopListeningFromMicLocked");
@@ -838,6 +878,7 @@
         mHotwordDetectionConnection.stopListeningFromMicLocked();
     }
 
+    @GuardedBy("this")
     public void triggerHardwareRecognitionEventForTestLocked(
             SoundTrigger.KeyphraseRecognitionEvent event,
             IHotwordRecognitionStatusCallback callback) {
@@ -852,6 +893,7 @@
         mHotwordDetectionConnection.triggerHardwareRecognitionEventForTestLocked(event, callback);
     }
 
+    @GuardedBy("this")
     public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
             IHotwordRecognitionStatusCallback callback) {
         if (DEBUG) {
@@ -876,6 +918,7 @@
         return null;
     }
 
+    @GuardedBy("this")
     boolean isIsolatedProcessLocked(@NonNull ServiceInfo serviceInfo) {
         return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
                 && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
@@ -889,6 +932,7 @@
         mHotwordDetectionConnection.forceRestart();
     }
 
+    @GuardedBy("this")
     void setDebugHotwordLoggingLocked(boolean logging) {
         if (mHotwordDetectionConnection == null) {
             Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active");
@@ -897,6 +941,7 @@
         mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging);
     }
 
+    @GuardedBy("this")
     void resetHotwordDetectionConnectionLocked() {
         if (DEBUG) {
             Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
@@ -911,6 +956,7 @@
         mHotwordDetectionConnection = null;
     }
 
+    @GuardedBy("this")
     public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!mValid) {
             pw.print("  NOT VALID: ");
@@ -947,6 +993,7 @@
         }
     }
 
+    @GuardedBy("this")
     void startLocked() {
         Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
         intent.setComponent(mComponent);
@@ -971,6 +1018,7 @@
         }
     }
 
+    @GuardedBy("this")
     void shutdownLocked() {
         // If there is an active session, cancel it to allow it to clean up its window and other
         // state.
@@ -998,6 +1046,7 @@
         }
     }
 
+    @GuardedBy("this")
     void notifySoundModelsChangedLocked() {
         if (mService == null) {
             Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 2c0087e..b59e855 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -17,10 +17,12 @@
 
 import static android.telephony.euicc.EuiccCardManager.ResetOption;
 
+import android.Manifest;
 import android.annotation.CallSuper;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
@@ -132,13 +134,25 @@
      * @see android.telephony.euicc.EuiccManager#ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS
      * The difference is this one is used by system to bring up the LUI.
      */
+    @RequiresPermission(Manifest.permission.BIND_EUICC_SERVICE)
     public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
             "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
 
     /** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */
+    @RequiresPermission(Manifest.permission.BIND_EUICC_SERVICE)
     public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION =
             "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
 
+    /** @see android.telephony.euicc.EuiccManager#ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS */
+    @RequiresPermission(Manifest.permission.BIND_EUICC_SERVICE)
+    public static final String ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS =
+            "android.service.euicc.action.TRANSFER_EMBEDDED_SUBSCRIPTIONS";
+
+    /** @see android.telephony.euicc.EuiccManager#ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION */
+    @RequiresPermission(Manifest.permission.BIND_EUICC_SERVICE)
+    public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION =
+            "android.service.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
+
     /**
      * @see android.telephony.euicc.EuiccManager#ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED. This is
      * a protected intent that can only be sent by the system, and requires the
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f0f230f..acfc194 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3866,13 +3866,12 @@
      * NR_SA - NR SA is unmetered for sub-6 frequencies
      * NR_SA_MMWAVE - NR SA is unmetered for mmwave frequencies
      *
-     * Note that this config only applies if an unmetered SubscriptionPlan is set via
-     * {@link SubscriptionManager#setSubscriptionPlans(int, List)} or an unmetered override is set
+     * Note that this config only applies if an unmetered SubscriptionPlan is set via {@link
+     * SubscriptionManager#setSubscriptionPlans(int, List, long)} or an unmetered override is set
      * via {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, int[], long)}
      * or {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, long)}.
      * If neither SubscriptionPlans nor an override are set, then no network types can be unmetered
      * regardless of the value of this config.
-     * TODO: remove other unmetered keys and replace with this
      * @hide
      */
     public static final String KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY =
@@ -3887,73 +3886,18 @@
      * NR_SA - NR SA is unmetered when roaming for sub-6 frequencies
      * NR_SA_MMWAVE - NR SA is unmetered when roaming for mmwave frequencies
      *
-     * Note that this config only applies if an unmetered SubscriptionPlan is set via
-     * {@link SubscriptionManager#setSubscriptionPlans(int, List)} or an unmetered override is set
+     * Note that this config only applies if an unmetered SubscriptionPlan is set via {@link
+     * SubscriptionManager#setSubscriptionPlans(int, List, long)} or an unmetered override is set
      * via {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, int[], long)}
      * or {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, long)}.
      * If neither SubscriptionPlans nor an override are set, then no network types can be unmetered
      * when roaming regardless of the value of this config.
-     * TODO: remove KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL and replace with this
      * @hide
      */
     public static final String KEY_ROAMING_UNMETERED_NETWORK_TYPES_STRING_ARRAY =
             "roaming_unmetered_network_types_string_array";
 
     /**
-     * Whether NR (non-standalone) should be unmetered for all frequencies.
-     * If either {@link #KEY_UNMETERED_NR_NSA_MMWAVE_BOOL} or
-     * {@link #KEY_UNMETERED_NR_NSA_SUB6_BOOL} are true, then this value will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_NSA_BOOL = "unmetered_nr_nsa_bool";
-
-    /**
-     * Whether NR (non-standalone) frequencies above 6GHz (millimeter wave) should be unmetered.
-     * If this is true, then the value for {@link #KEY_UNMETERED_NR_NSA_BOOL} will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_NSA_MMWAVE_BOOL = "unmetered_nr_nsa_mmwave_bool";
-
-    /**
-     * Whether NR (non-standalone) frequencies below 6GHz (sub6) should be unmetered.
-     * If this is true, then the value for {@link #KEY_UNMETERED_NR_NSA_BOOL} will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_NSA_SUB6_BOOL = "unmetered_nr_nsa_sub6_bool";
-
-    /**
-     * Whether NR (non-standalone) should be unmetered when the device is roaming.
-     * If false, then the values for {@link #KEY_UNMETERED_NR_NSA_BOOL},
-     * {@link #KEY_UNMETERED_NR_NSA_MMWAVE_BOOL}, {@link #KEY_UNMETERED_NR_NSA_SUB6_BOOL},
-     * and unmetered {@link SubscriptionPlan} will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL =
-            "unmetered_nr_nsa_when_roaming_bool";
-
-    /**
-     * Whether NR (standalone) should be unmetered for all frequencies.
-     * If either {@link #KEY_UNMETERED_NR_SA_MMWAVE_BOOL} or
-     * {@link #KEY_UNMETERED_NR_SA_SUB6_BOOL} are true, then this value will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_SA_BOOL = "unmetered_nr_sa_bool";
-
-    /**
-     * Whether NR (standalone) frequencies above 6GHz (millimeter wave) should be unmetered.
-     * If this is true, then the value for {@link #KEY_UNMETERED_NR_SA_BOOL} will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_SA_MMWAVE_BOOL = "unmetered_nr_sa_mmwave_bool";
-
-    /**
-     * Whether NR (standalone) frequencies below 6GHz (sub6) should be unmetered.
-     * If this is true, then the value for {@link #KEY_UNMETERED_NR_SA_BOOL} will be ignored.
-     * @hide
-     */
-    public static final String KEY_UNMETERED_NR_SA_SUB6_BOOL = "unmetered_nr_sa_sub6_bool";
-
-    /**
      * Support ASCII 7-BIT encoding for long SMS. This carrier config is used to enable
      * this feature.
      * @hide
@@ -10043,13 +9987,6 @@
         sDefaults.putStringArray(KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY, new String[] {
                 "NR_NSA", "NR_NSA_MMWAVE", "NR_SA", "NR_SA_MMWAVE"});
         sDefaults.putStringArray(KEY_ROAMING_UNMETERED_NETWORK_TYPES_STRING_ARRAY, new String[0]);
-        sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_BOOL, false);
-        sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, false);
-        sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_SUB6_BOOL, false);
-        sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL, false);
-        sDefaults.putBoolean(KEY_UNMETERED_NR_SA_BOOL, false);
-        sDefaults.putBoolean(KEY_UNMETERED_NR_SA_MMWAVE_BOOL, false);
-        sDefaults.putBoolean(KEY_UNMETERED_NR_SA_SUB6_BOOL, false);
         sDefaults.putBoolean(KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_WIFI_CALLING_ICON_IN_STATUS_BAR_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, false);
diff --git a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
index 8308821..c2f5b8f 100644
--- a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
+++ b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
@@ -23,6 +23,7 @@
 import android.telephony.euicc.EuiccCardManager;
 import android.telephony.euicc.EuiccManager;
 import android.telephony.ims.ImsManager;
+import android.telephony.satellite.SatelliteManager;
 
 import com.android.internal.util.Preconditions;
 
@@ -98,6 +99,11 @@
                 context -> SmsManager.getSmsManagerForContextAndSubscriptionId(context,
                         SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
         );
+        SystemServiceRegistry.registerContextAwareService(
+                Context.SATELLITE_SERVICE,
+                SatelliteManager.class,
+                context -> new SatelliteManager(context)
+        );
     }
 
     /** @hide */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0921834..c65faea 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -9432,7 +9432,6 @@
             ALLOWED_NETWORK_TYPES_REASON_POWER,
             ALLOWED_NETWORK_TYPES_REASON_CARRIER,
             ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
-            ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AllowedNetworkTypesReason {
@@ -9471,15 +9470,6 @@
     public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3;
 
     /**
-     * To indicate allowed network type change is requested by an update to the
-     * {@link android.os.UserManager.DISALLOW_CELLULAR_2G} user restriction.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS = 4;
-
-    /**
      * Set the allowed network types of the device and provide the reason triggering the allowed
      * network change.
      * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or
@@ -9503,7 +9493,6 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE} permissions.
      * <ol>
      *     <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}</li>
-     *     <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS}</li>
      * </ol>
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -9578,7 +9567,6 @@
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER:
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER:
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
-            case ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS:
                 return true;
         }
         return false;
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index cdb7d7c..1e21e9a9 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -82,6 +82,47 @@
     public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
             "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
 
+
+    /**
+     * Intent action to transfer an embedded subscriptions.
+     *
+     * <p> Action sent by apps (such as the Settings app) to the Telephony framework to transfer an
+     * embedded subscription.
+     *
+     * <p> Requires that the calling app has the
+     * {@code android.Manifest.permission#MODIFY_PHONE_STATE} permission.
+     *
+     * <p>The activity will immediately finish with {@link android.app.Activity#RESULT_CANCELED} if
+     * {@link #isEnabled} is false.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public static final String ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS =
+            "android.telephony.euicc.action.TRANSFER_EMBEDDED_SUBSCRIPTIONS";
+
+    /**
+     * Intent action to convert the physical subscription to an embedded subscription.
+     *
+     * <p> Action sent by apps (such as the Settings app) to the Telephony framework to convert
+     * physical sim to embedded sim.
+     *
+     * <p> Requires that the calling app has the
+     * {@code android.Manifest.permission#MODIFY_PHONE_STATE} permission.
+     *
+     * <p>The activity will immediately finish with {@link android.app.Activity#RESULT_CANCELED} if
+     * {@link #isEnabled} is false.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION =
+            "android.telephony.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
+
     /**
      * Broadcast Action: The eUICC OTA status is changed.
      * <p class="note">
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
new file mode 100644
index 0000000..5c3fa32
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.telephony.satellite.PointingInfo;
+
+/**
+ * Callback for position updates from the satellite service.
+ * @hide
+ */
+oneway interface ISatellitePositionUpdateCallback {
+    void onSatellitePositionUpdate(in PointingInfo pointingInfo);
+    void onMessageTransferStateUpdate(in int state);
+}
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.aidl b/telephony/java/android/telephony/satellite/PointingInfo.aidl
new file mode 100644
index 0000000..7ff95cd
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/PointingInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+parcelable PointingInfo;
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
new file mode 100644
index 0000000..d015427
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
+import android.util.ArrayMap;
+
+import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.ITelephony;
+import com.android.telephony.Rlog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+/**
+ * Manages satellite operations such as provisioning, pointing, messaging, location sharing, etc.
+ * To get the object, call {@link Context#getSystemService(Context.SATELLITE_SERVICE)}.
+ * To create an instance of {@link SatelliteManager} associated with a specific subscription ID,
+ * call {@link #createForSubscriptionId(int)}.
+ *
+ * @hide
+ */
+@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE)
+public class SatelliteManager {
+    private static final String TAG = "SatelliteManager";
+
+    /**
+     * Map of all SatellitePositionUpdateCallback and their associated callback ids.
+     */
+    private final Map<SatellitePositionUpdateCallback, Integer> mSatellitePositionUpdateCallbacks =
+            new ArrayMap<>();
+
+    /**
+     * AtomicInteger for the id of the next SatellitePositionUpdateCallback.
+     */
+    private final AtomicInteger mSatellitePositionUpdateCallbackId = new AtomicInteger(0);
+
+    /**
+     * The subscription ID for this SatelliteManager.
+     */
+    private final int mSubId;
+
+    /**
+     * Context this SatelliteManager is for.
+     */
+    @Nullable private final Context mContext;
+
+    /**
+     * Create an instance of the SatelliteManager.
+     *
+     * @param context The context the SatelliteManager belongs to.
+     */
+    public SatelliteManager(@Nullable Context context) {
+        // TODO: replace DEFAULT_SUBSCRIPTION_ID with DEFAULT_SATELLITE_SUBSCRIPTION_ID
+        this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+    }
+
+    /**
+     * Create a new SatelliteManager associated with the given subscription ID.
+     *
+     * @param subId The subscription ID to create the SatelliteManager with.
+     * @return A SatelliteManager that uses the given subscription ID for all calls.
+     */
+    @NonNull public SatelliteManager createForSubscriptionId(int subId) {
+        return new SatelliteManager(mContext, subId);
+    }
+
+    /**
+     * Create an instance of the SatelliteManager associated with a particular subscription.
+     *
+     * @param context The context the SatelliteManager belongs to.
+     * @param subId The subscription ID associated with the SatelliteManager.
+     */
+    private SatelliteManager(@Nullable Context context, int subId) {
+        mContext = context;
+        mSubId = subId;
+    }
+
+    /**
+     * Successful response.
+     */
+    public static final int SATELLITE_SERVICE_SUCCESS = 0;
+    /**
+     * Satellite server is not reachable.
+     */
+    public static final int SATELLITE_SERVICE_SERVER_NOT_REACHABLE = 1;
+    /**
+     * Error received from the satellite server.
+     */
+    public static final int SATELLITE_SERVICE_SERVER_ERROR = 2;
+    /**
+     * Unexpected telephony internal error.
+     */
+    public static final int SATELLITE_SERVICE_TELEPHONY_INTERNAL_ERROR = 3;
+    /**
+     * Modem error received from the satellite service.
+     */
+    public static final int SATELLITE_SERVICE_MODEM_ERROR = 4;
+    /**
+     * System error received from the satellite service.
+     */
+    public static final int SATELLITE_SERVICE_SYSTEM_ERROR = 5;
+    /**
+     * Invalid arguments passed.
+     */
+    public static final int SATELLITE_SERVICE_INVALID_ARGUMENTS = 6;
+    /**
+     * Invalid modem state.
+     */
+    public static final int SATELLITE_SERVICE_INVALID_MODEM_STATE = 7;
+    /**
+     * Invalid SIM state.
+     */
+    public static final int SATELLITE_SERVICE_INVALID_SIM_STATE = 8;
+    /**
+     * Invalid state.
+     */
+    public static final int SATELLITE_SERVICE_INVALID_STATE = 9;
+    /**
+     * Satellite service is unavailable.
+     */
+    public static final int SATELLITE_SERVICE_NOT_AVAILABLE = 10;
+    /**
+     * Satellite service is not supported by the device or OS.
+     */
+    public static final int SATELLITE_SERVICE_NOT_SUPPORTED = 11;
+    /**
+     * Satellite service is rate limited.
+     */
+    public static final int SATELLITE_SERVICE_RATE_LIMITED = 12;
+    /**
+     * Satellite service has no memory available.
+     */
+    public static final int SATELLITE_SERVICE_NO_MEMORY = 13;
+    /**
+     * Satellite service has no resources available.
+     */
+    public static final int SATELLITE_SERVICE_NO_RESOURCES = 14;
+    /**
+     * Failed to send a request to the satellite service.
+     */
+    public static final int SATELLITE_SERVICE_REQUEST_FAILED = 15;
+    /**
+     * Failed to send a request to the satellite service for the given subscription ID.
+     */
+    public static final int SATELLITE_SERVICE_INVALID_SUBSCRIPTION_ID = 16;
+    /**
+     * Error received from satellite service.
+     */
+    public static final int SATELLITE_SERVICE_ERROR = 17;
+
+    /**
+     * Satellite service is disabled on the requested subscription.
+     */
+    public static final int SATELLITE_SERVICE_DISABLED = 18;
+
+    /** @hide */
+    @IntDef(prefix = {"SATELLITE_SERVICE_"}, value = {
+            SATELLITE_SERVICE_SUCCESS,
+            SATELLITE_SERVICE_SERVER_NOT_REACHABLE,
+            SATELLITE_SERVICE_SERVER_ERROR,
+            SATELLITE_SERVICE_TELEPHONY_INTERNAL_ERROR,
+            SATELLITE_SERVICE_MODEM_ERROR,
+            SATELLITE_SERVICE_SYSTEM_ERROR,
+            SATELLITE_SERVICE_INVALID_ARGUMENTS,
+            SATELLITE_SERVICE_INVALID_MODEM_STATE,
+            SATELLITE_SERVICE_INVALID_SIM_STATE,
+            SATELLITE_SERVICE_INVALID_STATE,
+            SATELLITE_SERVICE_NOT_AVAILABLE,
+            SATELLITE_SERVICE_NOT_SUPPORTED,
+            SATELLITE_SERVICE_RATE_LIMITED,
+            SATELLITE_SERVICE_NO_MEMORY,
+            SATELLITE_SERVICE_NO_RESOURCES,
+            SATELLITE_SERVICE_REQUEST_FAILED,
+            SATELLITE_SERVICE_INVALID_SUBSCRIPTION_ID,
+            SATELLITE_SERVICE_ERROR,
+            SATELLITE_SERVICE_DISABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SatelliteServiceResult {}
+
+    /**
+     * Message transfer is waiting to acquire.
+     */
+    public static final int SATELLITE_MESSAGE_TRANSFER_STATE_WAITING_TO_ACQUIRE = 0;
+    /**
+     * Message is being sent.
+     */
+    public static final int SATELLITE_MESSAGE_TRANSFER_STATE_SENDING = 1;
+    /**
+     * Message is being received.
+     */
+    public static final int SATELLITE_MESSAGE_TRANSFER_STATE_RECEIVING = 2;
+    /**
+     * Message transfer is being retried.
+     */
+    public static final int SATELLITE_MESSAGE_TRANSFER_STATE_RETRYING = 3;
+    /**
+     * Message transfer is complete.
+     */
+    public static final int SATELLITE_MESSAGE_TRANSFER_STATE_COMPLETE = 4;
+
+    /** @hide */
+    @IntDef(prefix = {"SATELLITE_MESSAGE_TRANSFER_STATE_"}, value = {
+            SATELLITE_MESSAGE_TRANSFER_STATE_WAITING_TO_ACQUIRE,
+            SATELLITE_MESSAGE_TRANSFER_STATE_SENDING,
+            SATELLITE_MESSAGE_TRANSFER_STATE_RECEIVING,
+            SATELLITE_MESSAGE_TRANSFER_STATE_RETRYING,
+            SATELLITE_MESSAGE_TRANSFER_STATE_COMPLETE
+    })
+    public @interface SatelliteMessageTransferState {}
+
+    /**
+     * Callback for position updates from the satellite service.
+     */
+    public interface SatellitePositionUpdateCallback {
+        /**
+         * Called when the satellite position changes.
+         *
+         * @param pointingInfo The pointing info containing the satellite location.
+         */
+        void onSatellitePositionUpdate(@NonNull PointingInfo pointingInfo);
+
+        /**
+         * Called when satellite message transfer state changes.
+         *
+         * @param state The new message transfer state.
+         */
+        void onMessageTransferStateUpdate(@SatelliteMessageTransferState int state);
+    }
+
+    /**
+     * Start receiving satellite position updates.
+     * This can be called by the pointing UI when the user starts pointing to the satellite.
+     * Modem should continue to report the pointing input as the device or satellite moves.
+     * Satellite position updates are started only on {@link #SATELLITE_SERVICE_SUCCESS}.
+     * All other results indicate that this operation failed.
+     *
+     * @param executor The executor to run callbacks on.
+     * @param callback The callback to notify of changes in satellite position.
+     * @return The result of the operation.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteServiceResult public int startSatellitePositionUpdates(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SatellitePositionUpdateCallback callback) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                int id;
+                if (mSatellitePositionUpdateCallbacks.containsKey(callback)) {
+                    id = mSatellitePositionUpdateCallbacks.get(callback);
+                } else {
+                    id = mSatellitePositionUpdateCallbackId.getAndIncrement();
+                }
+                int result = telephony.startSatellitePositionUpdates(mSubId, id,
+                        new ISatellitePositionUpdateCallback.Stub() {
+                            @Override
+                            public void onSatellitePositionUpdate(
+                                    @NonNull PointingInfo pointingInfo) {
+                                logd("onSatellitePositionUpdate: pointingInfo=" + pointingInfo);
+                                executor.execute(() ->
+                                        callback.onSatellitePositionUpdate(pointingInfo));
+                            }
+
+                            @Override
+                            public void onMessageTransferStateUpdate(
+                                    @SatelliteMessageTransferState int state) {
+                                logd("onMessageTransferStateUpdate: state=" + state);
+                                executor.execute(() ->
+                                        callback.onMessageTransferStateUpdate(state));
+                            }
+                        });
+                if (result == SATELLITE_SERVICE_SUCCESS) {
+                    mSatellitePositionUpdateCallbacks.put(callback, id);
+                }
+                return result;
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("startSatellitePositionUpdates RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_SERVICE_REQUEST_FAILED;
+    }
+
+    /**
+     * Stop receiving satellite position updates.
+     * This can be called by the pointing UI when the user stops pointing to the satellite.
+     * Satellite position updates are stopped only on {@link #SATELLITE_SERVICE_SUCCESS}.
+     * All other results indicate that this operation failed.
+     *
+     * @param callback The callback that was passed in {@link
+     *                 #startSatellitePositionUpdates(Executor, SatellitePositionUpdateCallback)}.
+     * @return The result of the operation.
+     * @throws IllegalArgumentException if the callback is invalid.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteServiceResult public int stopSatellitePositionUpdates(
+            @NonNull SatellitePositionUpdateCallback callback) {
+        if (!mSatellitePositionUpdateCallbacks.containsKey(callback)) {
+            throw new IllegalArgumentException(
+                    "startSatellitePositionUpdates was never called with the callback provided.");
+        }
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                int result = telephony.stopSatellitePositionUpdates(mSubId,
+                        mSatellitePositionUpdateCallbacks.get(callback));
+                if (result == SATELLITE_SERVICE_SUCCESS) {
+                    mSatellitePositionUpdateCallbacks.remove(callback);
+                    // TODO: Notify SmsHandler that pointing UI stopped
+                }
+                return result;
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("stopSatellitePositionUpdates RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_SERVICE_REQUEST_FAILED;
+    }
+
+    /**
+     * Get maximum number of characters per text message on satellite.
+     * @param executor - The executor on which the result listener will be called.
+     * @param resultListener - Listener that will be called when the operation is successful.
+     *                       If this method returns {@link #SATELLITE_SERVICE_SUCCESS}, listener
+     *                       will be called with maximum characters limit.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     *
+     * @return The result of the operation
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteServiceResult
+    public int getMaxCharactersPerSatelliteTextMessage(@NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<Integer> resultListener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(resultListener);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        executor.execute(() -> Binder.withCleanCallingIdentity(
+                                () -> resultListener.accept(result)));
+                    }
+                };
+
+                return telephony.getMaxCharactersPerSatelliteTextMessage(mSubId, internalCallback);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("getMaxCharactersPerSatelliteTextMessage() RemoteException:" + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_SERVICE_REQUEST_FAILED;
+    }
+
+    private static ITelephony getITelephony() {
+        ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
+                .getTelephonyServiceManager()
+                .getTelephonyServiceRegisterer()
+                .get());
+        if (binder == null) {
+            throw new RuntimeException("Could not find Telephony Service.");
+        }
+        return binder;
+    }
+
+    private static void logd(@NonNull String log) {
+        Rlog.d(TAG, log);
+    }
+
+    private static void loge(@NonNull String log) {
+        Rlog.e(TAG, log);
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 48b1657..5486365 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -66,6 +66,7 @@
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IRcsConfigCallback;
+import android.telephony.satellite.ISatellitePositionUpdateCallback;
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.IBooleanConsumer;
@@ -2636,14 +2637,14 @@
      */
     boolean isRemovableEsimDefaultEuicc(String callingPackage);
 
-     /**
-      * Get the component name of the default app to direct respond-via-message intent for the
-      * user associated with this subscription, update the cache if there is no respond-via-message
-      * application currently configured for this user.
-      * @return component name of the app and class to direct Respond Via Message intent to, or
-      * {@code null} if the functionality is not supported.
-      * @hide
-      */
+    /**
+     * Get the component name of the default app to direct respond-via-message intent for the
+     * user associated with this subscription, update the cache if there is no respond-via-message
+     * application currently configured for this user.
+     * @return component name of the app and class to direct Respond Via Message intent to, or
+     * {@code null} if the functionality is not supported.
+     * @hide
+     */
     ComponentName getDefaultRespondViaMessageApplication(int subId, boolean updateIfNeeded);
 
     /**
@@ -2667,12 +2668,12 @@
     void setNullCipherAndIntegrityEnabled(boolean enabled);
 
     /**
-    * Get whether the radio is able to connect with null ciphering or integrity
-    * algorithms. Note that this retrieves the phone-global preference and not
-    * the state of the radio.
-    *
-    * @hide
-    */
+     * Get whether the radio is able to connect with null ciphering or integrity
+     * algorithms. Note that this retrieves the phone-global preference and not
+     * the state of the radio.
+     *
+     * @hide
+     */
     boolean isNullCipherAndIntegrityPreferenceEnabled();
 
     /**
@@ -2696,5 +2697,21 @@
     /**
      * Get the carrier restriction status of the device.
      */
-     void getCarrierRestrictionStatus(IIntegerConsumer internalCallback, String packageName);
-}
+    void getCarrierRestrictionStatus(IIntegerConsumer internalCallback, String packageName);
+
+    /**
+     * Start receiving satellite pointing updates.
+     */
+    int startSatellitePositionUpdates(int subId, int callbackId,
+            in ISatellitePositionUpdateCallback callback);
+
+    /**
+     * Stop receiving satellite pointing updates.
+     */
+    int stopSatellitePositionUpdates(int subId, int callbackId);
+
+    /**
+     * Get maximum number of characters per text message on satellite.
+     */
+    int getMaxCharactersPerSatelliteTextMessage(int subId, IIntegerConsumer internalCallback);
+}
\ No newline at end of file
diff --git a/tests/BinaryTransparencyHostTest/Android.bp b/tests/BinaryTransparencyHostTest/Android.bp
index 142e3dd..dc6bdff 100644
--- a/tests/BinaryTransparencyHostTest/Android.bp
+++ b/tests/BinaryTransparencyHostTest/Android.bp
@@ -35,6 +35,7 @@
     data: [
         ":BinaryTransparencyTestApp",
         ":EasterEgg",
+        ":com.android.apex.cts.shim.v2_rebootless_prebuilt",
     ],
     test_suites: [
         "general-tests",
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index 84bed92..8db3d00 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -17,6 +17,7 @@
 package android.transparency.test;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -29,14 +30,22 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+
 // TODO: Add @Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
     private static final String PACKAGE_NAME = "android.transparency.test.app";
 
+    private static final String JOB_ID = "1740526926";
+
+    /** Waiting time for the job to be scheduled */
+    private static final int JOB_CREATION_MAX_SECONDS = 5;
+
     @After
     public void tearDown() throws Exception {
         uninstallPackage("com.android.egg");
+        uninstallRebootlessApex();
     }
 
     @Test
@@ -64,6 +73,28 @@
     }
 
     @Test
+    public void testRebootlessApexUpdateTriggersJobScheduling() throws Exception {
+        cancelPendingJob();
+        installRebootlessApex();
+
+        // Verify
+        expectJobToBeScheduled();
+        // Just cancel since we can't verifying very meaningfully.
+        cancelPendingJob();
+    }
+
+    @Test
+    public void testPreloadUpdateTriggersJobScheduling() throws Exception {
+        cancelPendingJob();
+        installPackage("EasterEgg.apk");
+
+        // Verify
+        expectJobToBeScheduled();
+        // Just cancel since we can't verifying very meaningfully.
+        cancelPendingJob();
+    }
+
+    @Test
     public void testMeasureMbas() throws Exception {
         // TODO(265244016): figure out a way to install an MBA
     }
@@ -74,4 +105,47 @@
         options.setTestMethodName(method);
         runDeviceTests(options);
     }
+
+    private void cancelPendingJob() throws DeviceNotAvailableException {
+        CommandResult result = getDevice().executeShellV2Command(
+                "cmd jobscheduler cancel android " + JOB_ID);
+        assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+    }
+
+    private void expectJobToBeScheduled() throws Exception {
+        for (int i = 0; i < JOB_CREATION_MAX_SECONDS; i++) {
+            CommandResult result = getDevice().executeShellV2Command(
+                    "cmd jobscheduler get-job-state android " + JOB_ID);
+            String state = result.getStdout().toString();
+            if (state.startsWith("unknown")) {
+                // The job hasn't been scheduled yet. So try again.
+                TimeUnit.SECONDS.sleep(1);
+            } else if (result.getExitCode() != 0) {
+                fail("Failing due to unexpected job state: " + result);
+            } else {
+                // The job exists, which is all we care about here
+                return;
+            }
+        }
+        fail("Timed out waiting for the job to be scheduled");
+    }
+
+    private void installRebootlessApex() throws Exception {
+        installPackage("com.android.apex.cts.shim.v2_rebootless.apex", "--force-non-staged");
+    }
+
+    private void uninstallRebootlessApex() throws DeviceNotAvailableException {
+        // Reboot only if the APEX is not the pre-install one.
+        CommandResult result = getDevice().executeShellV2Command(
+                "pm list packages -f --apex-only |grep com.android.apex.cts.shim");
+        assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+        if (result.getStdout().contains("/data/apex/active/")) {
+            uninstallPackage("com.android.apex.cts.shim");
+            getDevice().reboot();
+
+            // Reboot enforces SELinux. Make it permissive again.
+            CommandResult runResult = getDevice().executeShellV2Command("setenforce 0");
+            assertTrue(runResult.getStatus() == CommandStatus.SUCCESS);
+        }
+    }
 }