Merge "Change SharedConnectivityService's permission check to use checkCallingOrSelfPermission" into udc-dev
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 11c13c4..f6c18d6 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1447,7 +1447,6 @@
          * reasonable estimates should use the sentinel value
          * {@link JobInfo#NETWORK_BYTES_UNKNOWN}.
          * </ul>
-         * TODO(255371817): update documentation to reflect how this data will be used
          * Note that the system may choose to delay jobs with large network
          * usage estimates when the device has a poor network connection, in
          * order to save battery and possible network costs.
@@ -1478,6 +1477,7 @@
          * @see JobInfo#getEstimatedNetworkUploadBytes()
          * @see JobWorkItem#JobWorkItem(android.content.Intent, long, long)
          */
+        // TODO(b/255371817): update documentation to reflect how this data will be used
         public Builder setEstimatedNetworkBytes(@BytesLong long downloadBytes,
                 @BytesLong long uploadBytes) {
             mNetworkDownloadBytes = downloadBytes;
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 242b52c..32502ed 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.usage.UsageStatsManager;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -102,6 +103,7 @@
      * The user stopped the job via some UI (eg. Task Manager).
      * @hide
      */
+    @TestApi
     public static final int INTERNAL_STOP_REASON_USER_UI_STOP =
             JobProtoEnums.INTERNAL_STOP_REASON_USER_UI_STOP; // 11.
     /**
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
index ce5ade5..5a12142 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
@@ -21,6 +21,7 @@
 
 import android.annotation.NonNull;
 import android.app.Notification;
+import android.app.job.JobParameters;
 import android.app.job.JobService;
 import android.content.pm.UserPackage;
 import android.os.UserHandle;
@@ -80,7 +81,7 @@
         final NotificationDetails oldDetails = mNotificationDetails.get(hostingContext);
         if (oldDetails != null && oldDetails.notificationId != notificationId) {
             // App is switching notification IDs. Remove association with the old one.
-            removeNotificationAssociation(hostingContext);
+            removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED);
         }
         final int userId = UserHandle.getUserId(callingUid);
         // TODO(260848384): ensure apps can't cancel the notification for user-initiated job
@@ -100,7 +101,8 @@
         mNotificationDetails.put(hostingContext, details);
     }
 
-    void removeNotificationAssociation(@NonNull JobServiceContext hostingContext) {
+    void removeNotificationAssociation(@NonNull JobServiceContext hostingContext,
+            @JobParameters.StopReason int stopReason) {
         final NotificationDetails details = mNotificationDetails.remove(hostingContext);
         if (details == null) {
             return;
@@ -114,7 +116,10 @@
         ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId);
         if (associatedContexts == null || associatedContexts.isEmpty()) {
             // No more jobs using this notification. Apply the final job stop policy.
-            if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE) {
+            // If the user attempted to stop the job/app, then always remove the notification
+            // so the user doesn't get confused about the app state.
+            if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE
+                    || stopReason == JobParameters.STOP_REASON_USER) {
                 final String packageName = details.userPackage.packageName;
                 mNotificationManagerInternal.cancelNotification(
                         packageName, packageName, details.appUid, details.appPid, /* tag */ null,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 4088a48..bf3789f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1476,7 +1476,8 @@
                     /* timingDelayConstraintSatisfied */ false,
                     /* isDeviceIdle */ false,
                     /* hasConnectivityConstraintSatisfied */ false,
-                    /* hasContentTriggerConstraintSatisfied */ false);
+                    /* hasContentTriggerConstraintSatisfied */ false,
+                    0);
 
             // If the job is immediately ready to run, then we can just immediately
             // put it in the pending list and try to schedule it.  This is especially
@@ -1894,7 +1895,8 @@
                     cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
                     cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
                     cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
-                    cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
+                    cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
+                    0);
         }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 1d0fdd9..4357d4f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -670,6 +670,7 @@
                 + " [PACKAGE] [JOB_ID]");
         pw.println("    Trigger immediate timeout of currently executing jobs, as if their");
         pw.println("    execution timeout had expired.");
+        pw.println("    This is the equivalent of calling `stop -s 3 -i 3`.");
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         all users");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index ceb47ea..29ab455 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -461,7 +461,8 @@
                     job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
                     job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
                     job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
-                    job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
+                    job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
+                    mExecutionStartTimeElapsed - job.enqueueTime);
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
                 // Use the context's ID to distinguish traces since there'll only be one job
                 // running per context.
@@ -1356,7 +1357,8 @@
                 completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
                 completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
                 completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
-                completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
+                completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
+                0);
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                     getId());
@@ -1373,7 +1375,7 @@
                     JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
                     String.valueOf(mRunningJob.getJobId()));
         }
-        mNotificationCoordinator.removeNotificationAssociation(this);
+        mNotificationCoordinator.removeNotificationAssociation(this, reschedulingStopReason);
         if (mWakeLock != null) {
             mWakeLock.release();
         }
diff --git a/core/api/current.txt b/core/api/current.txt
index 2f1d81d..2161186 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5279,8 +5279,8 @@
   }
 
   public class BroadcastOptions {
+    ctor public BroadcastOptions();
     method public boolean isShareIdentityEnabled();
-    method @NonNull public static android.app.BroadcastOptions makeBasic();
     method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean);
     method @NonNull public android.os.Bundle toBundle();
   }
@@ -13577,17 +13577,26 @@
   }
 
   public final class CreateCredentialRequest implements android.os.Parcelable {
-    ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean, boolean);
     method public boolean alwaysSendAppInfoToProvider();
     method public int describeContents();
     method @NonNull public android.os.Bundle getCandidateQueryData();
     method @NonNull public android.os.Bundle getCredentialData();
+    method @Nullable public String getOrigin();
     method @NonNull public String getType();
     method public boolean isSystemProviderRequired();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialRequest> CREATOR;
   }
 
+  public static final class CreateCredentialRequest.Builder {
+    ctor public CreateCredentialRequest.Builder(@NonNull android.os.Bundle, @NonNull android.os.Bundle);
+    method @NonNull public android.credentials.CreateCredentialRequest build();
+    method @NonNull public android.credentials.CreateCredentialRequest.Builder setAlwaysSendAppInfoToProvider(boolean);
+    method @NonNull public android.credentials.CreateCredentialRequest.Builder setIsSystemProviderRequired(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public android.credentials.CreateCredentialRequest.Builder setOrigin(@NonNull String);
+    method @NonNull public android.credentials.CreateCredentialRequest.Builder setType(@NonNull String);
+  }
+
   public final class CreateCredentialResponse implements android.os.Parcelable {
     ctor public CreateCredentialResponse(@NonNull android.os.Bundle);
     method public int describeContents();
@@ -13619,9 +13628,7 @@
   public final class CredentialManager {
     method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
     method public void createCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
-    method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public void createCredentialWithOrigin(@NonNull android.credentials.CreateCredentialRequest, @Nullable String, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
     method public void getCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
-    method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public void getCredentialWithOrigin(@NonNull android.credentials.GetCredentialRequest, @Nullable String, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
     method public boolean isEnabledCredentialProviderService(@NonNull android.content.ComponentName);
     method public void registerCredentialDescription(@NonNull android.credentials.RegisterCredentialDescriptionRequest);
     method public void unregisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest);
@@ -13656,6 +13663,7 @@
     method public int describeContents();
     method @NonNull public java.util.List<android.credentials.CredentialOption> getCredentialOptions();
     method @NonNull public android.os.Bundle getData();
+    method @Nullable public String getOrigin();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialRequest> CREATOR;
   }
@@ -13666,6 +13674,7 @@
     method @NonNull public android.credentials.GetCredentialRequest build();
     method @NonNull public android.credentials.GetCredentialRequest.Builder setAlwaysSendAppInfoToProvider(boolean);
     method @NonNull public android.credentials.GetCredentialRequest.Builder setCredentialOptions(@NonNull java.util.List<android.credentials.CredentialOption>);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public android.credentials.GetCredentialRequest.Builder setOrigin(@NonNull String);
   }
 
   public final class GetCredentialResponse implements android.os.Parcelable {
@@ -19361,7 +19370,7 @@
   public final class OutputConfiguration implements android.os.Parcelable {
     ctor public OutputConfiguration(@NonNull android.view.Surface);
     ctor public OutputConfiguration(int, @NonNull android.view.Surface);
-    ctor public OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
+    ctor public <T> OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
     method public void addSensorPixelModeUsed(int);
     method public void addSurface(@NonNull android.view.Surface);
     method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader);
@@ -32404,6 +32413,8 @@
     field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4
     field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1
     field public static final String EXTRA_BATTERY_LOW = "battery_low";
+    field public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS";
+    field public static final String EXTRA_CYCLE_COUNT = "android.os.extra.CYCLE_COUNT";
     field public static final String EXTRA_HEALTH = "health";
     field public static final String EXTRA_ICON_SMALL = "icon-small";
     field public static final String EXTRA_LEVEL = "level";
@@ -40561,7 +40572,9 @@
 
   public final class CallingAppInfo implements android.os.Parcelable {
     ctor public CallingAppInfo(@NonNull String, @NonNull android.content.pm.SigningInfo);
+    ctor public CallingAppInfo(@NonNull String, @NonNull android.content.pm.SigningInfo, @Nullable String);
     method public int describeContents();
+    method @Nullable public String getOrigin();
     method @NonNull public String getPackageName();
     method @NonNull public android.content.pm.SigningInfo getSigningInfo();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -42053,6 +42066,7 @@
   }
 
   public final class CallControl {
+    method public void answer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method @NonNull public android.os.ParcelUuid getCallId();
     method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
@@ -42065,8 +42079,7 @@
   public interface CallControlCallback {
     method public void onAnswer(int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onCallStreamingStarted(@NonNull java.util.function.Consumer<java.lang.Boolean>);
-    method public void onDisconnect(@NonNull java.util.function.Consumer<java.lang.Boolean>);
-    method public void onReject(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onDisconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onSetActive(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onSetInactive(@NonNull java.util.function.Consumer<java.lang.Boolean>);
   }
@@ -50383,12 +50396,6 @@
     field public static final int VERTICAL_GRAVITY_MASK = 112; // 0x70
   }
 
-  public class HandwritingDelegateConfiguration {
-    ctor public HandwritingDelegateConfiguration(@IdRes int, @NonNull Runnable);
-    method public int getDelegatorViewId();
-    method @NonNull public Runnable getInitiationCallback();
-  }
-
   public class HapticFeedbackConstants {
     field public static final int CLOCK_TICK = 4; // 0x4
     field public static final int CONFIRM = 16; // 0x10
@@ -51986,6 +51993,8 @@
     method @Nullable public CharSequence getAccessibilityPaneTitle();
     method @IdRes public int getAccessibilityTraversalAfter();
     method @IdRes public int getAccessibilityTraversalBefore();
+    method @NonNull public String getAllowedHandwritingDelegatePackageName();
+    method @NonNull public String getAllowedHandwritingDelegatorPackageName();
     method public float getAlpha();
     method public android.view.animation.Animation getAnimation();
     method @Nullable public android.graphics.Matrix getAnimationMatrix();
@@ -52041,7 +52050,7 @@
     method public float getHandwritingBoundsOffsetLeft();
     method public float getHandwritingBoundsOffsetRight();
     method public float getHandwritingBoundsOffsetTop();
-    method @Nullable public android.view.HandwritingDelegateConfiguration getHandwritingDelegateConfiguration();
+    method @Nullable public Runnable getHandwritingDelegatorCallback();
     method public final boolean getHasOverlappingRendering();
     method public final int getHeight();
     method public void getHitRect(android.graphics.Rect);
@@ -52197,6 +52206,7 @@
     method public boolean isFocused();
     method public final boolean isFocusedByDefault();
     method public boolean isForceDarkAllowed();
+    method public boolean isHandwritingDelegate();
     method public boolean isHapticFeedbackEnabled();
     method public boolean isHardwareAccelerated();
     method public boolean isHorizontalFadingEdgeEnabled();
@@ -52367,6 +52377,8 @@
     method public void setAccessibilityTraversalBefore(@IdRes int);
     method public void setActivated(boolean);
     method public void setAllowClickWhenDisabled(boolean);
+    method public void setAllowedHandwritingDelegatePackage(@NonNull String);
+    method public void setAllowedHandwritingDelegatorPackage(@NonNull String);
     method public void setAlpha(@FloatRange(from=0.0, to=1.0) float);
     method public void setAnimation(android.view.animation.Animation);
     method public void setAnimationMatrix(@Nullable android.graphics.Matrix);
@@ -52409,7 +52421,7 @@
     method public void setForegroundTintList(@Nullable android.content.res.ColorStateList);
     method public void setForegroundTintMode(@Nullable android.graphics.PorterDuff.Mode);
     method public void setHandwritingBoundsOffsets(float, float, float, float);
-    method public void setHandwritingDelegateConfiguration(@Nullable android.view.HandwritingDelegateConfiguration);
+    method public void setHandwritingDelegatorCallback(@Nullable Runnable);
     method public void setHapticFeedbackEnabled(boolean);
     method public void setHasTransientState(boolean);
     method public void setHorizontalFadingEdgeEnabled(boolean);
@@ -52422,6 +52434,7 @@
     method public void setImportantForAutofill(int);
     method public void setImportantForContentCapture(int);
     method public void setIsCredential(boolean);
+    method public void setIsHandwritingDelegate(boolean);
     method public void setKeepScreenOn(boolean);
     method public void setKeyboardNavigationCluster(boolean);
     method public void setLabelFor(@IdRes int);
@@ -55620,6 +55633,8 @@
   }
 
   public final class InputMethodManager {
+    method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View);
+    method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String);
     method public void dispatchKeyEventFromInputMethod(@Nullable android.view.View, @NonNull android.view.KeyEvent);
     method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
     method @Nullable public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfo();
@@ -55641,6 +55656,8 @@
     method public boolean isInputMethodSuppressingSpellChecker();
     method public boolean isStylusHandwritingAvailable();
     method @Deprecated public boolean isWatchingCursor(android.view.View);
+    method public void prepareStylusHandwritingDelegation(@NonNull android.view.View);
+    method public void prepareStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String);
     method public void restartInput(android.view.View);
     method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle);
     method @Deprecated public void setAdditionalInputMethodSubtypes(@NonNull String, @NonNull android.view.inputmethod.InputMethodSubtype[]);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8c0a917..47d9ab6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -173,6 +173,7 @@
     field public static final String MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED = "android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED";
     field public static final String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE";
     field public static final String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
+    field public static final String MANAGE_CLIPBOARD_ACCESS_NOTIFICATION = "android.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION";
     field public static final String MANAGE_CLOUDSEARCH = "android.permission.MANAGE_CLOUDSEARCH";
     field public static final String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
     field public static final String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS";
@@ -842,6 +843,7 @@
     method public int getPendingIntentBackgroundActivityStartMode();
     method public boolean isDeferUntilActive();
     method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
+    method @Deprecated @NonNull public static android.app.BroadcastOptions makeBasic();
     method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
     method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
     method @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
@@ -3399,6 +3401,8 @@
   }
 
   public class ClipboardManager extends android.text.ClipboardManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION) public boolean areClipboardAccessNotificationsEnabled();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION) public void setClipboardAccessNotificationsEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.SET_CLIP_SOURCE) public void setPrimaryClipAsPackage(@NonNull android.content.ClipData, @NonNull String);
   }
 
@@ -10202,6 +10206,14 @@
 
   public class BatteryManager {
     method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setChargingStateUpdateDelayMillis(int);
+    field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9
+    field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8
+    field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7
+    field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa
+    field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3
+    field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2
+    field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4
+    field public static final int CHARGING_POLICY_DEFAULT = 1; // 0x1
     field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
     field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
   }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a53fa34..640506d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -725,6 +725,14 @@
 
 }
 
+package android.app.job {
+
+  public class JobParameters implements android.os.Parcelable {
+    field public static final int INTERNAL_STOP_REASON_USER_UI_STOP = 11; // 0xb
+  }
+
+}
+
 package android.app.prediction {
 
   public final class AppPredictor {
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index c628ec4..d859f3f 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -451,6 +451,15 @@
      */
     public static final int SUBREASON_SDK_SANDBOX_DIED = 27;
 
+    /**
+     * The process was killed because it was an SDK sandbox process that was either not usable or
+     * was no longer being used; this would be set only when the reason is {@link #REASON_OTHER}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_SDK_SANDBOX_NOT_NEEDED = 28;
+
     // If there is any OEM code which involves additional app kill reasons, it should
     // be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
 
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 6301ad7..a99815c 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -556,7 +556,12 @@
     @Override
     public ActivityInfo getActivityInfo(ComponentName className, ComponentInfoFlags flags)
             throws NameNotFoundException {
-        final int userId = getUserId();
+        return getActivityInfoAsUser(className, flags, getUserId());
+    }
+
+    @Override
+    public ActivityInfo getActivityInfoAsUser(ComponentName className,
+            ComponentInfoFlags flags, @UserIdInt int userId) throws NameNotFoundException {
         try {
             ActivityInfo ai = mPM.getActivityInfo(className,
                     updateFlagsForComponent(flags.getValue(), userId, null), userId);
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f35bdfb..fe40a4c 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -262,7 +262,12 @@
      * Creates a basic {@link BroadcastOptions} with no options initially set.
      *
      * @return an instance of {@code BroadcastOptions} against which options can be set
+     *
+     * @deprecated Use {@link BroadcastOptions#BroadcastOptions()} instead.
+     * @hide
      */
+    @Deprecated
+    @SystemApi
     public static @NonNull BroadcastOptions makeBasic() {
         BroadcastOptions opts = new BroadcastOptions();
         return opts;
@@ -280,7 +285,10 @@
         return opts;
     }
 
-    private BroadcastOptions() {
+    /**
+     * Creates a new {@code BroadcastOptions} with no options initially set.
+     */
+    public BroadcastOptions() {
         super();
         resetTemporaryAppAllowlist();
     }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a203ae2..1c0be68 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1923,6 +1923,7 @@
 
     private ComponentName startServiceCommon(Intent service, boolean requireForeground,
             UserHandle user) {
+        // Keep this in sync with ActivityManagerLocal.startSdkSandboxService
         try {
             validateServiceIntent(service);
             service.prepareToLeaveProcess(this);
@@ -1964,6 +1965,7 @@
     }
 
     private boolean stopServiceCommon(Intent service, UserHandle user) {
+        // // Keep this in sync with ActivityManagerLocal.stopSdkSandboxService
         try {
             validateServiceIntent(service);
             service.prepareToLeaveProcess(this);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index e97e711..d62e15a 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -65,12 +65,12 @@
 import android.os.IBinder;
 import android.os.IProgressListener;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
 import android.os.StrictMode;
 import android.os.WorkSource;
 import android.service.voice.IVoiceInteractionSession;
 import android.view.IRecentsAnimationRunner;
 import android.view.IRemoteAnimationRunner;
-import android.view.IWindowFocusObserver;
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationAdapter;
 import android.window.IWindowOrganizerController;
@@ -349,12 +349,13 @@
     /**
      * Prepare the back navigation in the server. This setups the leashed for sysui to animate
      * the back gesture and returns the data needed for the animation.
-     * @param focusObserver a remote callback to nofify shell when the focused window lost focus.
+     * @param navigationObserver a remote callback to nofify shell when the focused window is gone,
+                                 or an unexpected transition has happened on the navigation target.
      * @param adaptor a remote animation to be run for the back navigation plays the animation.
      * @return Returns the back navigation info.
      */
     android.window.BackNavigationInfo startBackNavigation(
-            in IWindowFocusObserver focusObserver, in BackAnimationAdapter adaptor);
+            in RemoteCallback navigationObserver, in BackAnimationAdapter adaptor);
 
     /**
      * registers a callback to be invoked when the screen is captured.
diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java
index b62f766..bb9a95c 100644
--- a/core/java/android/app/LocaleManager.java
+++ b/core/java/android/app/LocaleManager.java
@@ -198,13 +198,14 @@
      * the override config, and stored in a system file for future access.
      *
      * <p><b>Note:</b> Using this function, applications can update their list of supported
-     * locales while running, without an update of the application’s software. For more
-     * information, see TODO(b/261528306): add link to guide.
+     * locales while running, without an update of the application’s software.
      *
      * <p>Applications can remove the override LocaleConfig with a {@code null} object.
      *
      * @param localeConfig the desired {@link LocaleConfig} for the calling app.
      */
+     // Add following to last Note: when guide is written:
+     // For more information, see TODO(b/261528306): add link to guide.
     @UserHandleAware
     public void setOverrideLocaleConfig(@Nullable LocaleConfig localeConfig) {
         try {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ef5cd93..440ee20 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5758,7 +5758,7 @@
             List<Notification.Action> nonContextualActions = getNonContextualActions();
 
             int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
-            boolean emphazisedMode = mN.fullScreenIntent != null
+            boolean emphasizedMode = mN.fullScreenIntent != null
                     || p.mCallStyleActions
                     || ((mN.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0);
 
@@ -5771,7 +5771,7 @@
                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
                         R.dimen.call_notification_collapsible_indent);
             }
-            big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
+            big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
             if (numActions > 0 && !p.mHideActions) {
                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
                 big.setViewVisibility(R.id.actions, View.VISIBLE);
@@ -5783,12 +5783,12 @@
                     boolean actionHasValidInput = hasValidRemoteInput(action);
                     validRemoteInput |= actionHasValidInput;
 
-                    final RemoteViews button = generateActionButton(action, emphazisedMode, p);
-                    if (actionHasValidInput && !emphazisedMode) {
+                    final RemoteViews button = generateActionButton(action, emphasizedMode, p);
+                    if (actionHasValidInput && !emphasizedMode) {
                         // Clear the drawable
                         button.setInt(R.id.action0, "setBackgroundResource", 0);
                     }
-                    if (emphazisedMode && i > 0) {
+                    if (emphasizedMode && i > 0) {
                         // Clear start margin from non-first buttons to reduce the gap between them.
                         //  (8dp remaining gap is from all buttons' standard 4dp inset).
                         button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
@@ -7475,10 +7475,10 @@
             Resources resources = context.getResources();
             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
             if (mPictureIcon != null) {
-                int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
+                int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
                         ? R.dimen.notification_big_picture_max_height_low_ram
                         : R.dimen.notification_big_picture_max_height);
-                int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
+                int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
                         ? R.dimen.notification_big_picture_max_width_low_ram
                         : R.dimen.notification_big_picture_max_width);
                 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 50a7da1..5ee10a5 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -115,6 +115,20 @@
     String SHELL_COMMAND_CONFIRM_TIME = "confirm_time";
 
     /**
+     * A shell command that clears the network time signal used by {@link
+     * SystemClock#currentNetworkTimeClock()}.
+     * @hide
+     */
+    String SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME = "clear_system_clock_network_time";
+
+    /**
+     * A shell command that sets the network time signal used by {@link
+     * SystemClock#currentNetworkTimeClock()}.
+     * @hide
+     */
+    String SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME = "set_system_clock_network_time";
+
+    /**
      * A shared utility method to create a {@link ManualTimeSuggestion}.
      *
      * @hide
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index e981581..2b400c1f 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -155,6 +155,11 @@
     AttributionSource(@NonNull Parcel in) {
         this(AttributionSourceState.CREATOR.createFromParcel(in));
 
+        if (!Binder.isDirectlyHandlingTransaction()) {
+            throw new SecurityException("AttributionSource should be unparceled during a binder "
+                    + "transaction for proper verification.");
+        }
+
         // Since we just unpacked this object as part of it transiting a Binder
         // call, this is the perfect time to enforce that its UID and PID can be trusted
         enforceCallingUidAndPid();
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 06a3b1e..8a22ce3 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -135,6 +135,39 @@
     }
 
     /**
+     * Determine if the Clipboard Access Notifications are enabled
+     *
+     * @return true if notifications are enabled, false otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION)
+    public boolean areClipboardAccessNotificationsEnabled() {
+        try {
+            return mService.areClipboardAccessNotificationsEnabledForUser(mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     *
+     * Set the enable state of the Clipboard Access Notifications
+     * @param enable Whether to enable notifications
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION)
+    public void setClipboardAccessNotificationsEnabled(boolean enable) {
+        try {
+            mService.setClipboardAccessNotificationsEnabledForUser(enable, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Sets the current primary clip on the clipboard.  This is the clip that
      * is involved in normal cut and paste operations.
      *
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
index b2216d4..fe7798f 100644
--- a/core/java/android/content/IClipboard.aidl
+++ b/core/java/android/content/IClipboard.aidl
@@ -48,4 +48,8 @@
 
     String getPrimaryClipSource(String callingPackage, String attributionTag, int userId,
             int deviceId);
+
+    boolean areClipboardAccessNotificationsEnabledForUser(int userId);
+
+    void setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId);
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5818ed7..85daf15 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3234,8 +3234,9 @@
     /**
      * Broadcast Action: The receiver's effective locale has changed.
      *
-     * This happens when the device locale, or the receiving app's locale
-     * (set via {@link android.app.LocaleManager#setApplicationLocales}) changed.
+     * This happens when the device locale, the receiving app's locale
+     * (set via {@link android.app.LocaleManager#setApplicationLocales}) or language tags
+     * of Regional preferences changed.
      *
      * Can be received by manifest-declared receivers.
      *
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index afc2285..bd3cf5f 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -20,6 +20,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Build;
@@ -182,6 +185,28 @@
     private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
 
     /**
+     * An intent with action set as null used to always pass the action test during intent
+     * filter matching. This causes a lot of confusion and unexpected intent matches.
+     * Null action intents should be blocked when either the intent sender or receiver
+     * application targets U or higher.
+     *
+     * mBlockNullAction indicates whether the intent filter owner (intent receiver) is
+     * targeting U+. This value will be properly set by package manager when IntentFilters are
+     * passed to an application, so that when an application is trying to perform intent filter
+     * matching locally, the correct matching algorithm will be chosen.
+     *
+     * When an IntentFilter is sent to system server (e.g. for registering runtime receivers),
+     * the value set in mBlockNullAction will be ignored and overwritten with the correct
+     * value evaluated based on the Binder calling identity. This makes sure that the
+     * security enforcement cannot be bypassed by crafting a malicious IntentFilter.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long BLOCK_NULL_ACTION_INTENTS = 264497795;
+
+    /**
      * The filter {@link #setPriority} value at which system high-priority
      * receivers are placed; that is, receivers that should execute before
      * application code. Applications should never use filters with this or
@@ -2276,6 +2301,7 @@
         String type = resolve ? intent.resolveType(resolver) : intent.getType();
         return match(intent.getAction(), type, intent.getScheme(),
                      intent.getData(), intent.getCategories(), logTag,
+                     CompatChanges.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS),
                      false /* supportWildcards */, null /* ignoreActions */,
                      intent.getExtras());
     }
@@ -2328,6 +2354,7 @@
             Uri data, Set<String> categories, String logTag, boolean supportWildcards,
             @Nullable Collection<String> ignoreActions) {
         return match(action, type, scheme, data, categories, logTag, supportWildcards,
+                CompatChanges.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS),
                 ignoreActions, null /* extras */);
     }
 
@@ -2339,8 +2366,10 @@
      */
     public final int match(String action, String type, String scheme,
             Uri data, Set<String> categories, String logTag, boolean supportWildcards,
-            @Nullable Collection<String> ignoreActions, @Nullable Bundle extras) {
-        if (action != null && !matchAction(action, supportWildcards, ignoreActions)) {
+            boolean blockNullAction, @Nullable Collection<String> ignoreActions,
+            @Nullable Bundle extras) {
+        if ((action == null && blockNullAction)
+                || !matchAction(action, supportWildcards, ignoreActions)) {
             if (false) Log.v(
                 logTag, "No matching action " + action + " for " + this);
             return NO_MATCH_ACTION;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 8acdf51..a6a6215 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1711,8 +1711,8 @@
          * performed on the session. In case of device reboot or data loader transient failure
          * before the session has been finalized, you may commit the session again.
          * <p>
-         * If the installer is the device owner or the affiliated profile owner, there will be no
-         * user intervention.
+         * If the installer is the device owner, the affiliated profile owner, or has received
+         * user pre-approval of this session, there will be no user intervention.
          *
          * @param statusReceiver Called when the state of the session changes. Intents
          *                       sent to this receiver contain {@link #EXTRA_STATUS}. Refer to the
@@ -1722,6 +1722,7 @@
          *             {@link #openWrite(String, long, long)} are still open.
          *
          * @see android.app.admin.DevicePolicyManager
+         * @see #requestUserPreapproval
          */
         public void commit(@NonNull IntentSender statusReceiver) {
             try {
@@ -1987,14 +1988,22 @@
          * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES REQUEST_INSTALL_PACKAGES}
          * permission, they can request the approval from users before
          * {@link Session#commit(IntentSender)} is called. This may require user intervention as
-         * well. The result of the request will be reported through the given callback.
+         * well. When user intervention is required, installers will receive a
+         * {@link #STATUS_PENDING_USER_ACTION} callback, and {@link #STATUS_SUCCESS} otherwise.
+         * In case that requesting user pre-approval is not available, installers will receive
+         * {@link #STATUS_FAILURE_BLOCKED} instead. Note that if the users decline the request,
+         * this session will be abandoned.
+         *
+         * If user intervention is required but never resolved, or requesting user
+         * pre-approval is not available, you may still call {@link Session#commit(IntentSender)}
+         * as the typical installation.
          *
          * @param details the adequate context to this session for requesting the approval from
          *                users prior to commit.
          * @param statusReceiver called when the state of the session changes.
-         *                       Intents sent to this receiver contain
-         *                       {@link #EXTRA_STATUS}. Refer to the individual
-         *                       status codes on how to handle them.
+         *                       Intents sent to this receiver contain {@link #EXTRA_STATUS}
+         *                       and the {@link #EXTRA_PRE_APPROVAL} would be {@code true}.
+         *                       Refer to the individual status codes on how to handle them.
          *
          * @throws IllegalArgumentException when {@link PreapprovalDetails} is {@code null}.
          * @throws IllegalArgumentException if {@link IntentSender} is {@code null}.
@@ -2003,6 +2012,7 @@
          * @throws IllegalStateException if called again after this method has been called on
          *                               this session.
          * @throws SecurityException when the caller does not own this session.
+         * @throws SecurityException if called after the session has been committed or abandoned.
          */
         public void requestUserPreapproval(@NonNull PreapprovalDetails details,
                 @NonNull IntentSender statusReceiver) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index db05b95..a0c620a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5720,6 +5720,17 @@
     }
 
     /**
+     * @hide
+     */
+    @NonNull
+    public ActivityInfo getActivityInfoAsUser(@NonNull ComponentName component,
+            @NonNull ComponentInfoFlags flags, @UserIdInt int userId)
+            throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getActivityInfoAsUser not implemented in subclass");
+    }
+
+    /**
      * Retrieve all of the information we know about a particular receiver
      * class.
      *
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index f3ad9d1..02b9308 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -361,7 +361,7 @@
     /**
      * Constant corresponding to {@code systemExempted} in
      * the {@link android.R.attr#foregroundServiceType} attribute.
-     * The system exmpted foreground service use cases.
+     * The system exempted foreground service use cases.
      *
      * <p class="note">Note, apps are allowed to use this type only in the following cases:
      * <ul>
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index 1e2a86f..b756a43 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -16,9 +16,14 @@
 
 package android.credentials;
 
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -69,6 +74,14 @@
     private final boolean mIsSystemProviderRequired;
 
     /**
+     * The origin of the calling app. Callers of this special API (e.g. browsers)
+     * can set this origin for an app different from their own, to be able to get credentials
+     * on behalf of that app.
+     */
+    @Nullable
+    private final String mOrigin;
+
+    /**
      * Returns the requested credential type.
      */
     @NonNull
@@ -123,6 +136,14 @@
         return mAlwaysSendAppInfoToProvider;
     }
 
+    /**
+     * Returns the origin of the calling app if set otherwise returns null.
+     */
+    @Nullable
+    public String getOrigin() {
+        return mOrigin;
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString8(mType);
@@ -130,6 +151,7 @@
         dest.writeBundle(mCandidateQueryData);
         dest.writeBoolean(mIsSystemProviderRequired);
         dest.writeBoolean(mAlwaysSendAppInfoToProvider);
+        dest.writeString8(mOrigin);
     }
 
     @Override
@@ -146,6 +168,7 @@
                 + ", isSystemProviderRequired=" + mIsSystemProviderRequired
                 + ", alwaysSendAppInfoToProvider="
                 + mAlwaysSendAppInfoToProvider
+                + ", origin=" + mOrigin
                 + "}";
     }
 
@@ -165,21 +188,26 @@
      *                                    the query phase, and will only be sent along
      *                                    with the final request, after the user has selected
      *                                    an entry on the UI.
+     * @param origin the origin of the calling app. Callers of this special setter (e.g. browsers)
+     *               can set this origin for an app different from their own, to be able to get
+     *               credentials on behalf of that app.
      *
      * @throws IllegalArgumentException If type is empty.
      */
-    public CreateCredentialRequest(
+    private CreateCredentialRequest(
             @NonNull String type,
             @NonNull Bundle credentialData,
             @NonNull Bundle candidateQueryData,
             boolean isSystemProviderRequired,
-            boolean alwaysSendAppInfoToProvider) {
+            boolean alwaysSendAppInfoToProvider,
+            @NonNull String origin) {
         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
         mCredentialData = requireNonNull(credentialData, "credentialData must not be null");
         mCandidateQueryData = requireNonNull(candidateQueryData,
                 "candidateQueryData must not be null");
         mIsSystemProviderRequired = isSystemProviderRequired;
         mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider;
+        mOrigin = origin;
     }
 
     private CreateCredentialRequest(@NonNull Parcel in) {
@@ -188,6 +216,7 @@
         Bundle candidateQueryData = in.readBundle();
         boolean isSystemProviderRequired = in.readBoolean();
         boolean alwaysSendAppInfoToProvider = in.readBoolean();
+        mOrigin = in.readString8();
 
         mType = type;
         AnnotationValidations.validate(NonNull.class, null, mType);
@@ -211,4 +240,104 @@
             return new CreateCredentialRequest(in);
         }
     };
+
+    /** A builder for {@link CreateCredentialRequest}. */
+    public static final class Builder {
+
+        private boolean mAlwaysSendAppInfoToProvider;
+
+        @NonNull
+        private String mType;
+
+        @NonNull
+        private final Bundle mCredentialData;
+
+        @NonNull
+        private final Bundle mCandidateQueryData;
+
+        private boolean mIsSystemProviderRequired;
+
+        private String mOrigin;
+
+        /**
+         * @param credentialData the full credential creation request data
+         * @param candidateQueryData the partial request data that will be sent to the provider
+         *                           during the initial creation candidate query stage
+         */
+        public Builder(@NonNull Bundle credentialData, @NonNull Bundle candidateQueryData) {
+            mCredentialData = requireNonNull(credentialData,
+                    "credentialData must not be null");
+            mCandidateQueryData = requireNonNull(candidateQueryData,
+                    "candidateQueryData must not be null");
+        }
+
+        /**
+         * Sets a true/false value to determine if the calling app info should be
+         * removed from the request that is sent to the providers.
+         *
+         * Developers must set this to false if they wish to remove the
+         * {@link android.service.credentials.CallingAppInfo} from the query phases requests that
+         * providers receive. Note that the calling app info will still be sent in the
+         * final phase after the user has made a selection on the UI.
+         *
+         * If not set, the default value will be true and the calling app info will be
+         * propagated to the providers in every phase.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public CreateCredentialRequest.Builder setAlwaysSendAppInfoToProvider(boolean value) {
+            mAlwaysSendAppInfoToProvider = value;
+            return this;
+        }
+
+        /**
+         * Sets the requested credential type.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public CreateCredentialRequest.Builder setType(@NonNull String type) {
+            mType = type;
+            return this;
+        }
+
+        /**
+         * Sets whether the request must only be fulfilled by a system provider.
+         * This defaults to false
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public CreateCredentialRequest.Builder setIsSystemProviderRequired(boolean value) {
+            mIsSystemProviderRequired = value;
+            return this;
+        }
+
+        /**
+         * Sets the origin of the calling app. Callers of this special setter (e.g. browsers)
+         * can set this origin for an app different from their own, to be able to get
+         * credentials on behalf of that app. The permission check only happens later when this
+         * instance is passed and processed by the Credential Manager.
+         */
+        @SuppressLint({"MissingGetterMatchingBuilder", "AndroidFrameworkRequiresPermission"})
+        @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
+        @NonNull
+        public CreateCredentialRequest.Builder setOrigin(@NonNull String origin) {
+            mOrigin = origin;
+            return this;
+        }
+
+        /**
+         * Builds a {@link GetCredentialRequest}.
+         *
+         * @throws IllegalArgumentException If credentialOptions is empty.
+         */
+        @NonNull
+        public CreateCredentialRequest build() {
+            Preconditions.checkStringNotEmpty(
+                    mType,
+                    "type must not be empty");
+
+            return new CreateCredentialRequest(mType, mCredentialData, mCandidateQueryData,
+                    mIsSystemProviderRequired, mAlwaysSendAppInfoToProvider, mOrigin);
+        }
+    }
 }
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index f9db5e8..bf34c1c 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -123,7 +123,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString(mType);
+        dest.writeString8(mType);
         dest.writeString(mFlattenedRequestString);
         dest.writeTypedList(mCredentialEntries, flags);
     }
@@ -151,16 +151,4 @@
     public List<CredentialEntry> getCredentialEntries() {
         return mCredentialEntries;
     }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mType, mFlattenedRequestString);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        return Objects.equals(mType, ((CredentialDescription) obj).getType())
-                && Objects.equals(mFlattenedRequestString, ((CredentialDescription) obj)
-                .getFlattenedRequestString());
-    }
 }
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index ff7fc36..f0230e7 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -123,6 +123,10 @@
      *
      * <p>The execution can potentially launch UI flows to collect user consent to using a
      * credential, display a picker when multiple credentials exist, etc.
+     * Callers (e.g. browsers) may optionally set origin in {@link GetCredentialRequest} for an
+     * app different from their own, to be able to get credentials on behalf of that app. They would
+     * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+     * to use this functionality
      *
      * @param request the request specifying type(s) of credentials to get from the user
      * @param activity the activity used to launch any UI needed
@@ -163,61 +167,14 @@
     }
 
     /**
-     * Launches the necessary flows to retrieve an app credential from the user, for the given
-     * origin.
-     *
-     * <p>The execution can potentially launch UI flows to collect user consent to using a
-     * credential, display a picker when multiple credentials exist, etc.
-     *
-     * @param request the request specifying type(s) of credentials to get from the user
-     * @param origin the origin of the calling app. Callers of this special API (e.g. browsers)
-     * can set this origin for an app different from their own, to be able to get credentials
-     * on behalf of that app.
-     * @param activity the activity used to launch any UI needed
-     * @param cancellationSignal an optional signal that allows for cancelling this call
-     * @param executor the callback will take place on this {@link Executor}
-     * @param callback the callback invoked when the request succeeds or fails
-     */
-    @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
-    public void getCredentialWithOrigin(
-            @NonNull GetCredentialRequest request,
-            @Nullable String origin,
-            @NonNull Activity activity,
-            @Nullable CancellationSignal cancellationSignal,
-            @CallbackExecutor @NonNull Executor executor,
-            @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
-        requireNonNull(request, "request must not be null");
-        requireNonNull(activity, "activity must not be null");
-        requireNonNull(executor, "executor must not be null");
-        requireNonNull(callback, "callback must not be null");
-
-        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
-            Log.w(TAG, "getCredential already canceled");
-            return;
-        }
-
-        ICancellationSignal cancelRemote = null;
-        try {
-            cancelRemote =
-                mService.executeGetCredentialWithOrigin(
-                    request,
-                    new GetCredentialTransport(activity, executor, callback),
-                    mContext.getOpPackageName(),
-                    origin);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-
-        if (cancellationSignal != null && cancelRemote != null) {
-            cancellationSignal.setRemote(cancelRemote);
-        }
-    }
-
-    /**
      * Launches the necessary flows to register an app credential for the user.
      *
      * <p>The execution can potentially launch UI flows to collect user consent to creating or
      * storing the new credential, etc.
+     * Callers (e.g. browsers) may optionally set origin in {@link CreateCredentialRequest} for an
+     * app different from their own, to be able to get credentials on behalf of that app. They would
+     * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+     * to use this functionality
      *
      * @param request the request specifying type(s) of credentials to get from the user
      * @param activity the activity used to launch any UI needed
@@ -259,58 +216,6 @@
     }
 
     /**
-     * Launches the necessary flows to register an app credential for the user.
-     *
-     * <p>The execution can potentially launch UI flows to collect user consent to creating or
-     * storing the new credential, etc.
-     *
-     * @param request the request specifying type(s) of credentials to get from the user, for the
-     * given origin
-     * @param origin the origin of the calling app. Callers of this special API (e.g. browsers)
-     * can set this origin for an app different from their own, to be able to get credentials
-     * on behalf of that app.
-     * @param activity the activity used to launch any UI needed
-     * @param cancellationSignal an optional signal that allows for cancelling this call
-     * @param executor the callback will take place on this {@link Executor}
-     * @param callback the callback invoked when the request succeeds or fails
-     */
-    @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
-    public void createCredentialWithOrigin(
-            @NonNull CreateCredentialRequest request,
-            @Nullable String origin,
-            @NonNull Activity activity,
-            @Nullable CancellationSignal cancellationSignal,
-            @CallbackExecutor @NonNull Executor executor,
-            @NonNull
-            OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
-        requireNonNull(request, "request must not be null");
-        requireNonNull(activity, "activity must not be null");
-        requireNonNull(executor, "executor must not be null");
-        requireNonNull(callback, "callback must not be null");
-
-        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
-            Log.w(TAG, "createCredential already canceled");
-            return;
-        }
-
-        ICancellationSignal cancelRemote = null;
-        try {
-            cancelRemote =
-                mService.executeCreateCredentialWithOrigin(
-                    request,
-                    new CreateCredentialTransport(activity, executor, callback),
-                    mContext.getOpPackageName(),
-                    origin);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-
-        if (cancellationSignal != null && cancelRemote != null) {
-            cancellationSignal.setRemote(cancelRemote);
-        }
-    }
-
-    /**
      * Clears the current user credential state from all credential providers.
      *
      * <p>You should invoked this api after your user signs out of your app to notify all credential
@@ -505,7 +410,19 @@
      *
      * @hide
      */
-    public static boolean isCredentialDescriptionApiEnabled() {
+    public static boolean isCredentialDescriptionApiEnabled(Context context) {
+        if (context == null) {
+            return false;
+        }
+        CredentialManager credentialManager =
+                (CredentialManager) context.getSystemService(Context.CREDENTIAL_SERVICE);
+        if (credentialManager != null) {
+            return credentialManager.isCredentialDescriptionApiEnabled();
+        }
+        return false;
+    }
+
+    private boolean isCredentialDescriptionApiEnabled() {
         return DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
     }
@@ -527,11 +444,6 @@
      */
     public void registerCredentialDescription(
             @NonNull RegisterCredentialDescriptionRequest request) {
-
-        if (!isCredentialDescriptionApiEnabled()) {
-            throw new UnsupportedOperationException("This API is not currently supported.");
-        }
-
         requireNonNull(request, "request must not be null");
 
         try {
@@ -550,11 +462,6 @@
      */
     public void unregisterCredentialDescription(
             @NonNull UnregisterCredentialDescriptionRequest request) {
-
-        if (!isCredentialDescriptionApiEnabled()) {
-            throw new UnsupportedOperationException("This API is not currently supported.");
-        }
-
         requireNonNull(request, "request must not be null");
 
         try {
diff --git a/core/java/android/credentials/GetCredentialRequest.java b/core/java/android/credentials/GetCredentialRequest.java
index bc92c7c..951cbe4 100644
--- a/core/java/android/credentials/GetCredentialRequest.java
+++ b/core/java/android/credentials/GetCredentialRequest.java
@@ -16,9 +16,13 @@
 
 package android.credentials;
 
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -49,6 +53,14 @@
     private final Bundle mData;
 
     /**
+     * The origin of the calling app. Callers of this special API (e.g. browsers)
+     * can set this origin for an app different from their own, to be able to get credentials
+     * on behalf of that app.
+     */
+    @Nullable
+    private String mOrigin;
+
+    /**
      * True/False value to determine if the calling app info should be
      * removed from the request that is sent to the providers.
      * Developers must set this to false if they wish to remove the
@@ -76,6 +88,14 @@
     }
 
     /**
+     * Returns the origin of the calling app if set otherwise returns null.
+     */
+    @Nullable
+    public String getOrigin() {
+        return mOrigin;
+    }
+
+    /**
      * Returns a value to determine if the calling app info should be always
      * sent to the provider in every phase (if true), or should be removed
      * from the query phase, and only sent as part of the request in the final phase,
@@ -90,6 +110,7 @@
         dest.writeTypedList(mCredentialOptions, flags);
         dest.writeBundle(mData);
         dest.writeBoolean(mAlwaysSendAppInfoToProvider);
+        dest.writeString8(mOrigin);
     }
 
     @Override
@@ -103,11 +124,12 @@
                 + ", data=" + mData
                 + ", alwaysSendAppInfoToProvider="
                 + mAlwaysSendAppInfoToProvider
+                + ", origin=" + mOrigin
                 + "}";
     }
 
     private GetCredentialRequest(@NonNull List<CredentialOption> credentialOptions,
-            @NonNull Bundle data, @NonNull boolean alwaysSendAppInfoToProvider) {
+            @NonNull Bundle data, @NonNull boolean alwaysSendAppInfoToProvider, String origin) {
         Preconditions.checkCollectionNotEmpty(
                 credentialOptions,
                 /*valueName=*/ "credentialOptions");
@@ -118,6 +140,7 @@
         mData = requireNonNull(data,
                 "data must not be null");
         mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider;
+        mOrigin = origin;
     }
 
     private GetCredentialRequest(@NonNull Parcel in) {
@@ -132,6 +155,7 @@
         AnnotationValidations.validate(NonNull.class, null, mData);
 
         mAlwaysSendAppInfoToProvider = in.readBoolean();
+        mOrigin = in.readString8();
     }
 
     @NonNull public static final Parcelable.Creator<GetCredentialRequest> CREATOR =
@@ -159,6 +183,8 @@
         @NonNull
         private boolean mAlwaysSendAppInfoToProvider = true;
 
+        private String mOrigin;
+
         /**
          * @param data the top request level data
          */
@@ -209,6 +235,20 @@
         }
 
         /**
+         * Sets the origin of the calling app. Callers of this special setter (e.g. browsers)
+         * can set this origin for an app different from their own, to be able to get
+         * credentials on behalf of that app. The permission check only happens later when this
+         * instance is passed and processed by the Credential Manager.
+         */
+        @SuppressLint({"MissingGetterMatchingBuilder", "AndroidFrameworkRequiresPermission"})
+        @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
+        @NonNull
+        public Builder setOrigin(@NonNull String origin) {
+            mOrigin = origin;
+            return this;
+        }
+
+        /**
          * Builds a {@link GetCredentialRequest}.
          *
          * @throws IllegalArgumentException If credentialOptions is empty.
@@ -222,7 +262,7 @@
                     mCredentialOptions,
                     /*valueName=*/ "credentialOptions");
             return new GetCredentialRequest(mCredentialOptions, mData,
-                    mAlwaysSendAppInfoToProvider);
+                    mAlwaysSendAppInfoToProvider, mOrigin);
         }
     }
 }
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 6d81d80..625fc8a 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -41,12 +41,8 @@
 
     @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
 
-    @nullable ICancellationSignal executeGetCredentialWithOrigin(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage, String origin);
-
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
 
-    @nullable ICancellationSignal executeCreateCredentialWithOrigin(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage, String origin);
-
     @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
 
     @nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 67634dc..3c10e81 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -78,7 +78,7 @@
                         Resources.getSystem()
                                 .getString(
                                         com.android.internal.R.string
-                                                .config_credentialManagerDialogComponent));
+                                                .config_credentialManagerReceiverComponent));
         intent.setComponent(componentName);
         intent.setAction(Constants.CREDMAN_ENABLED_PROVIDERS_UPDATED);
         return intent;
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 8e6576a..b9b310f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3660,8 +3660,8 @@
     /**
      * <p>An array of mandatory stream combinations which are applicable when device lists
      * {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}.
-     * This is an app-readable conversion of the preview stabilization mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p>
+     * This is an app-readable conversion of the preview stabilization mandatory stream
+     * combination {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index cca900a..1bf004a 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -541,7 +541,8 @@
     private static <T> boolean isOutputSupportedFor(Class<T> klass) {
         Objects.requireNonNull(klass, "klass must not be null");
 
-        if (klass == android.graphics.SurfaceTexture.class) {
+        if ((klass == android.graphics.SurfaceTexture.class) ||
+                (klass == android.view.SurfaceView.class)) {
             return true;
         }
 
@@ -725,6 +726,12 @@
      * backward compatible cameras whereas other output classes are not guaranteed to be supported.
      * </p>
      *
+     * <p>Starting with Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+     * {@link android.view.SurfaceView} classes are also guaranteed to be supported and include
+     * the same resolutions as {@link android.graphics.SurfaceTexture}.
+     * Clients must set the desired SurfaceView resolution by calling
+     * {@link android.view.SurfaceHolder#setFixedSize}.</p>
+     *
      * @param extension the extension type
      * @param klass     a non-{@code null} {@link Class} object reference
      * @return non-modifiable list of available sizes or an empty list if the Surface output is not
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 19719a8..fbc0184 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -142,24 +142,18 @@
                     PackageManager.PERMISSION_GRANTED;
         }
 
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
         mFoldStateListener = new FoldStateListener(context);
         try {
-            context.getSystemService(DeviceStateManager.class)
-                    .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+            context.getSystemService(DeviceStateManager.class).registerCallback(
+                    new HandlerExecutor(CameraManagerGlobal.get().getDeviceStateHandler()),
+                    mFoldStateListener);
         } catch (IllegalStateException e) {
             Log.v(TAG, "Failed to register device state listener!");
             Log.v(TAG, "Device state dependent characteristics updates will not be functional!");
-            mHandlerThread.quitSafely();
-            mHandler = null;
             mFoldStateListener = null;
         }
     }
 
-    private HandlerThread mHandlerThread;
-    private Handler mHandler;
     private FoldStateListener mFoldStateListener;
 
     /**
@@ -1645,6 +1639,9 @@
         private ICameraService mCameraService;
         private boolean mHasOpenCloseListenerPermission = false;
 
+        private HandlerThread mDeviceStateHandlerThread;
+        private Handler mDeviceStateHandler;
+
         // Singleton, don't allow construction
         private CameraManagerGlobal() { }
 
@@ -1658,6 +1655,18 @@
             return gCameraManager;
         }
 
+        public Handler getDeviceStateHandler() {
+            synchronized(mLock) {
+                if (mDeviceStateHandlerThread == null) {
+                    mDeviceStateHandlerThread = new HandlerThread(TAG);
+                    mDeviceStateHandlerThread.start();
+                    mDeviceStateHandler = new Handler(mDeviceStateHandlerThread.getLooper());
+                }
+
+                return mDeviceStateHandler;
+            }
+        }
+
         @Override
         public IBinder asBinder() {
             return this;
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 788302b..705afc5 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1000,24 +1000,25 @@
      * camera's crop region is set to maximum size, the FOV of the physical streams for the
      * ultrawide lens will be the same as the logical stream, by making the crop region
      * smaller than its active array size to compensate for the smaller focal length.</p>
-     * <p>There are two ways for the application to capture RAW images from a logical camera
-     * with RAW capability:</p>
+     * <p>For a logical camera, typically the underlying physical cameras have different RAW
+     * capabilities (such as resolution or CFA pattern). There are two ways for the
+     * application to capture RAW images from the logical camera:</p>
      * <ul>
-     * <li>Because the underlying physical cameras may have different RAW capabilities (such
-     * as resolution or CFA pattern), to maintain backward compatibility, when a RAW stream
-     * is configured, the camera device makes sure the default active physical camera remains
-     * active and does not switch to other physical cameras. (One exception is that, if the
-     * logical camera consists of identical image sensors and advertises multiple focalLength
-     * due to different lenses, the camera device may generate RAW images from different
-     * physical cameras based on the focalLength being set by the application.) This
-     * backward-compatible approach usually results in loss of optical zoom, to telephoto
-     * lens or to ultrawide lens.</li>
-     * <li>Alternatively, to take advantage of the full zoomRatio range of the logical camera,
-     * the application should use {@link android.hardware.camera2.MultiResolutionImageReader }
-     * to capture RAW images from the currently active physical camera. Because different
-     * physical camera may have different RAW characteristics, the application needs to use
-     * the characteristics and result metadata of the active physical camera for the
-     * relevant RAW metadata.</li>
+     * <li>If the logical camera has RAW capability, the application can create and use RAW
+     * streams in the same way as before. In case a RAW stream is configured, to maintain
+     * backward compatibility, the camera device makes sure the default active physical
+     * camera remains active and does not switch to other physical cameras. (One exception
+     * is that, if the logical camera consists of identical image sensors and advertises
+     * multiple focalLength due to different lenses, the camera device may generate RAW
+     * images from different physical cameras based on the focalLength being set by the
+     * application.) This backward-compatible approach usually results in loss of optical
+     * zoom, to telephoto lens or to ultrawide lens.</li>
+     * <li>Alternatively, if supported by the device,
+     * {@link android.hardware.camera2.MultiResolutionImageReader }
+     * can be used to capture RAW images from one of the underlying physical cameras (
+     * depending on current zoom level). Because different physical cameras may have
+     * different RAW characteristics, the application needs to use the characteristics
+     * and result metadata of the active physical camera for the relevant RAW metadata.</li>
      * </ul>
      * <p>The capture request and result metadata tags required for backward compatible camera
      * functionalities will be solely based on the logical camera capability. On the other
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 41c406d..db83e62 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -62,8 +62,6 @@
 import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.Pair;
-import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
@@ -270,6 +268,10 @@
                 }
             }
 
+            // The extension processing logic needs to be able to match images to capture results via
+            // image and result timestamps.
+            cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
+            cameraOutput.setReadoutTimestampEnabled(false);
             cameraOutput.setPhysicalCameraId(output.physicalCameraId);
             outputList.add(cameraOutput);
             mCameraConfigMap.put(cameraOutput.getSurface(), output);
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 3f85d44..c2b3656 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -60,7 +60,6 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Pair;
-import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
@@ -478,13 +477,21 @@
         ArrayList<CaptureStageImpl> sessionParamsList = new ArrayList<>();
         ArrayList<OutputConfiguration> outputList = new ArrayList<>();
         initializeRepeatingRequestPipeline();
-        outputList.add(new OutputConfiguration(mCameraRepeatingSurface));
+        OutputConfiguration previewOutput = new OutputConfiguration(mCameraRepeatingSurface);
+        // The extension processing logic needs to be able to match images to capture results via
+        // image and result timestamps.
+        previewOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
+        previewOutput.setReadoutTimestampEnabled(false);
+        outputList.add(previewOutput);
         CaptureStageImpl previewSessionParams = mPreviewExtender.onPresetSession();
         if (previewSessionParams != null) {
             sessionParamsList.add(previewSessionParams);
         }
         initializeBurstCapturePipeline();
-        outputList.add(new OutputConfiguration(mCameraBurstSurface));
+        OutputConfiguration captureOutput = new OutputConfiguration(mCameraBurstSurface);
+        captureOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
+        captureOutput.setReadoutTimestampEnabled(false);
+        outputList.add(captureOutput);
         CaptureStageImpl stillCaptureSessionParams = mImageExtender.onPresetSession();
         if (stillCaptureSessionParams != null) {
             sessionParamsList.add(stillCaptureSessionParams);
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index 5222408..08111c5 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.graphics.ImageFormat;
 import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
 import android.hardware.camera2.CameraExtensionCharacteristics;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
@@ -149,6 +150,7 @@
             SurfaceInfo surfaceInfo = querySurface(config.getSurface());
             if ((surfaceInfo.mFormat ==
                     CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT) ||
+                    ((surfaceInfo.mUsage & HardwareBuffer.USAGE_COMPOSER_OVERLAY) != 0) ||
                     // The default RGBA_8888 is also implicitly supported because camera will
                     // internally override it to
                     // 'CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT'
diff --git a/core/java/android/hardware/face/FaceAuthenticateOptions.java b/core/java/android/hardware/face/FaceAuthenticateOptions.java
index 4009fa7..1c6de04 100644
--- a/core/java/android/hardware/face/FaceAuthenticateOptions.java
+++ b/core/java/android/hardware/face/FaceAuthenticateOptions.java
@@ -52,7 +52,7 @@
     }
 
     /** The sensor id for this operation. */
-    private final int mSensorId;
+    private int mSensorId;
     private static int defaultSensorId() {
         return -1;
     }
@@ -299,6 +299,15 @@
     }
 
     /**
+     * The sensor id for this operation.
+     */
+    @DataClass.Generated.Member
+    public @NonNull FaceAuthenticateOptions setSensorId( int value) {
+        mSensorId = value;
+        return this;
+    }
+
+    /**
      * The package name for that operation that should be used for
      * {@link android.app.AppOpsManager} verification.
      *
@@ -610,10 +619,10 @@
     }
 
     @DataClass.Generated(
-            time = 1676508211385L,
+            time = 1677119626034L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/hardware/face/FaceAuthenticateOptions.java",
-            inputSignatures = "private final  int mUserId\nprivate final  int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final  int AUTHENTICATE_REASON_UNKNOWN\npublic static final  int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final  int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final  int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final  int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final  int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final  int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final  int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final  int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  int defaultDisplayState()\nprivate static  int defaultAuthenticateReason()\nprivate static  int defaultWakeReason()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+            inputSignatures = "private final  int mUserId\nprivate  int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final  int AUTHENTICATE_REASON_UNKNOWN\npublic static final  int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final  int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final  int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final  int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final  int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final  int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final  int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final  int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final  int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  int defaultDisplayState()\nprivate static  int defaultAuthenticateReason()\nprivate static  int defaultWakeReason()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 2857627..9d5073e 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -68,7 +68,7 @@
     // by BiometricService. To start authentication after the clients are ready, use
     // startPreparedClient().
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token,
+    void prepareForAuthentication(boolean requireConfirmation, IBinder token,
             long operationId, IBiometricSensorReceiver sensorReceiver,
             in FaceAuthenticateOptions options, long requestId, int cookie,
             boolean allowBackgroundAuthentication);
diff --git a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
index cecb317..763246e 100644
--- a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
+++ b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
@@ -46,7 +46,7 @@
     }
 
     /** The sensor id for this operation. */
-    private final int mSensorId;
+    private int mSensorId;
     private static int defaultSensorId() {
         return SENSOR_ID_ANY;
     }
@@ -176,6 +176,15 @@
     }
 
     /**
+     * The sensor id for this operation.
+     */
+    @DataClass.Generated.Member
+    public @NonNull FingerprintAuthenticateOptions setSensorId( int value) {
+        mSensorId = value;
+        return this;
+    }
+
+    /**
      * The package name for that operation that should be used for
      * {@link android.app.AppOpsManager} verification.
      *
@@ -433,10 +442,10 @@
     }
 
     @DataClass.Generated(
-            time = 1676508212083L,
+            time = 1677119626721L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java",
-            inputSignatures = "private final  int mUserId\nprivate final  int mSensorId\nprivate final  boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  boolean defaultIgnoreEnrollmentState()\nprivate static  int defaultDisplayState()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+            inputSignatures = "private final  int mUserId\nprivate  int mSensorId\nprivate final  boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static  int defaultUserId()\nprivate static  int defaultSensorId()\nprivate static  boolean defaultIgnoreEnrollmentState()\nprivate static  int defaultDisplayState()\nprivate static  java.lang.String defaultOpPackageName()\nprivate static  java.lang.String defaultAttributionTag()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index e3ae299..ec5749e 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -73,8 +73,8 @@
     // by BiometricService. To start authentication after the clients are ready, use
     // startPreparedClient().
     @EnforcePermission("MANAGE_BIOMETRIC")
-    void prepareForAuthentication(int sensorId, IBinder token, long operationId, int userId,
-            IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
+    void prepareForAuthentication(IBinder token, long operationId,
+            IBiometricSensorReceiver sensorReceiver, in FingerprintAuthenticateOptions options, long requestId,
             int cookie, boolean allowBackgroundAuthentication);
 
     // Starts authentication with the previously prepared client.
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 76f857b..6bc0f6e 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -147,6 +147,18 @@
     public static final String EXTRA_SEQUENCE = "seq";
 
     /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * Int value representing the battery charging cycle count.
+     */
+    public static final String EXTRA_CYCLE_COUNT = "android.os.extra.CYCLE_COUNT";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * Int value representing the battery charging status.
+     */
+    public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS";
+
+    /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_LEVEL_CHANGED}:
      * Contains list of Bundles representing battery events
      * @hide
@@ -190,6 +202,35 @@
     /** Power source is dock. */
     public static final int BATTERY_PLUGGED_DOCK = OsProtoEnums.BATTERY_PLUGGED_DOCK; // = 8
 
+    // values for "charge policy" property
+    /**
+     * Default policy (e.g. normal).
+     * @hide
+     */
+    @SystemApi
+    public static final int CHARGING_POLICY_DEFAULT = OsProtoEnums.CHARGING_POLICY_DEFAULT; // = 1
+    /**
+     * Optimized for battery health using static thresholds (e.g stop at 80%).
+     * @hide
+     */
+    @SystemApi
+    public static final int CHARGING_POLICY_ADAPTIVE_AON =
+                                            OsProtoEnums.CHARGING_POLICY_ADAPTIVE_AON; // = 2
+    /**
+     * Optimized for battery health using adaptive thresholds.
+     * @hide
+     */
+    @SystemApi
+    public static final int CHARGING_POLICY_ADAPTIVE_AC =
+                                            OsProtoEnums.CHARGING_POLICY_ADAPTIVE_AC; // = 3
+    /**
+     * Optimized for battery health, devices always connected to power.
+     * @hide
+     */
+    @SystemApi
+    public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
+                                            OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
+
     /** @hide */
     public static final int BATTERY_PLUGGED_ANY =
             BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS
@@ -254,6 +295,76 @@
      */
     public static final int BATTERY_PROPERTY_STATUS = 6;
 
+    /**
+     * Battery manufacturing date is reported in epoch. The 0 timepoint
+     * begins at midnight Coordinated Universal Time (UTC) on January 1, 1970.
+     * It is a long integer in seconds.
+     *
+     * <p class="note">
+     * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+     *
+     * Example: <code>
+     *  // The value returned from the API can be used to create a Date, used
+     *  // to set the time on a calendar and coverted to a string.
+     *  import java.util.Date;
+     *
+     *  mBatteryManager = mContext.getSystemService(BatteryManager.class);
+     *  final long manufacturingDate =
+     *      mBatteryManager.getLongProperty(BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE);
+     *  Date date = new Date(manufacturingDate);
+     *  Calendar calendar = Calendar.getInstance();
+     *  calendar.setTime(date);
+     * // Convert to yyyy-MM-dd HH:mm:ss format string
+     *  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+     *  String dateString = sdf.format(date);
+     * </code>
+     * @hide
+     */
+    @RequiresPermission(permission.BATTERY_STATS)
+    @SystemApi
+    public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7;
+
+    /**
+     * The date of first usage is reported in epoch. The 0 timepoint
+     * begins at midnight Coordinated Universal Time (UTC) on January 1, 1970.
+     * It is a long integer in seconds.
+     *
+     * <p class="note">
+     * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+     *
+     * {@link BATTERY_PROPERTY_MANUFACTURING_DATE for sample code}
+     * @hide
+     */
+    @RequiresPermission(permission.BATTERY_STATS)
+    @SystemApi
+    public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8;
+
+    /**
+     * Battery charging policy from a CHARGING_POLICY_* value..
+     *
+     * <p class="note">
+     * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+     *
+     * @hide
+     */
+    @RequiresPermission(permission.BATTERY_STATS)
+    @SystemApi
+    public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9;
+
+    /**
+     *
+     * Percentage representing the measured battery state of health (remaining
+     * estimated full charge capacity relative to the rated capacity in %).
+     *
+     * <p class="note">
+     * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+     *
+     * @hide
+     */
+    @RequiresPermission(permission.BATTERY_STATS)
+    @SystemApi
+    public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10;
+
     private final Context mContext;
     private final IBatteryStats mBatteryStats;
     private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
@@ -307,7 +418,6 @@
 
         try {
             BatteryProperty prop = new BatteryProperty();
-
             if (mBatteryPropertiesRegistrar.getProperty(id, prop) == 0)
                 ret = prop.getLong();
             else
diff --git a/core/java/android/os/CancellationSignalBeamer.java b/core/java/android/os/CancellationSignalBeamer.java
new file mode 100644
index 0000000..afb5ff7
--- /dev/null
+++ b/core/java/android/os/CancellationSignalBeamer.java
@@ -0,0 +1,325 @@
+/*
+ * 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 android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.system.SystemCleaner;
+
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.util.HashMap;
+
+/**
+ * A transport for {@link CancellationSignal}, but unlike
+ * {@link CancellationSignal#createTransport()} doesn't require pre-creating the transport in the
+ * target process. Instead, cancellation is forwarded over the same IPC surface as the cancellable
+ * request.
+ *
+ * <p><strong>Important:</strong> For this to work, the following invariants must be held up:
+ * <ul>
+ *     <li>A call to beam() <strong>MUST</strong> result in a call to close() on the result
+ *     (otherwise, the token will be leaked and cancellation isn't propagated), and that call
+ *     must happen after the call using the
+ *     token is sent (otherwise, any concurrent cancellation may be lost). It is strongly
+ *     recommended to use try-with-resources on the token.
+ *     <li>The cancel(), forget() and cancellable operations transporting the token must either
+ *     all be oneway on the same binder, or all be non-oneway to guarantee proper ordering.
+ *     <li>A {@link CancellationSignal} <strong>SHOULD</strong> be used only once, as there
+ *     can only be a single {@link android.os.CancellationSignal.OnCancelListener OnCancelListener}.
+ *
+ * </ul>
+ * <p>Caveats:
+ * <ul>
+ *     <li>Cancellation is only ever dispatched after the token is closed, and thus after the
+ *     call performing the cancellable operation (if the invariants are followed). The operation
+ *     must therefore not block the incoming binder thread, or cancellation won't be possible.
+ *     <li>Consequently, in the unlikely event that the sender dies right after beaming an already
+ *     cancelled {@link CancellationSignal}, the cancellation may be lost (unlike with
+ *     {@link CancellationSignal#createTransport()}).
+ *     <li>The forwarding OnCancelListener is set in the implied finally phase of try-with-resources
+ *         / when closing the token. If the receiver is in the same process, and the signal is
+ *         already cancelled, this may invoke the target's OnCancelListener during that phase.
+ * </ul>
+ *
+ *
+ * <p>Usage:
+ * <pre>
+ *  // Sender:
+ *
+ *  class FooManager {
+ *    var mCancellationSignalSender = new CancellationSignalBeamer.Sender() {
+ *      &#064;Override
+ *      public void onCancel(IBinder token) { remoteIFooService.onCancelToken(token); }
+ *
+ *      &#064;Override
+ *      public void onForget(IBinder token) { remoteIFooService.onForgetToken(token); }
+ *    };
+ *
+ *    public void doCancellableOperation(..., CancellationSignal cs) {
+ *      try (var csToken = mCancellationSignalSender.beam(cs)) {
+ *          remoteIFooService.doCancellableOperation(..., csToken);
+ *      }
+ *    }
+ *  }
+ *
+ *  // Receiver:
+ *
+ *  class FooManagerService extends IFooService.Stub {
+ *    var mCancellationSignalReceiver = new CancellationSignalBeamer.Receiver();
+ *
+ *    &#064;Override
+ *    public void doCancellableOperation(..., IBinder csToken) {
+ *      CancellationSignal cs = mCancellationSignalReceiver.unbeam(csToken))
+ *      // ...
+ *    }
+ *
+ *    &#064;Override
+ *    public void onCancelToken(..., IBinder csToken) {
+ *      mCancellationSignalReceiver.cancelToken(csToken))
+ *    }
+ *
+ *    &#064;Override
+ *    public void onForgetToken(..., IBinder csToken) {
+ *      mCancellationSignalReceiver.forgetToken(csToken))
+ *    }
+ *  }
+ *
+ * </pre>
+ *
+ * @hide
+ */
+public class CancellationSignalBeamer {
+
+    static final Cleaner sCleaner = SystemCleaner.cleaner();
+
+    /** The sending side of an {@link CancellationSignalBeamer} */
+    public abstract static class Sender {
+
+        /**
+         * Beams a {@link CancellationSignal} through an existing Binder interface.
+         *
+         * @param cs the {@code CancellationSignal} to beam, or {@code null}.
+         * @return an {@link IBinder} token. MUST be {@link CloseableToken#close}d <em>after</em>
+         *         the binder call transporting it to the remote process, best with
+         *         try-with-resources. {@code null} if {@code cs} was {@code null}.
+         */
+        // TODO(b/254888024): @MustBeClosed
+        @Nullable
+        public CloseableToken beam(@Nullable CancellationSignal cs) {
+            if (cs == null) {
+                return null;
+            }
+            return new Token(this, cs);
+        }
+
+        /**
+         * A {@link #beam}ed {@link CancellationSignal} was closed.
+         *
+         * MUST be forwarded to {@link Receiver#cancel} with proper ordering. See
+         * {@link CancellationSignalBeamer} for details.
+         */
+        public abstract void onCancel(IBinder token);
+
+        /**
+         * A {@link #beam}ed {@link CancellationSignal} was GC'd.
+         *
+         * MUST be forwarded to {@link Receiver#forget} with proper ordering. See
+         * {@link CancellationSignalBeamer} for details.
+         */
+        public abstract void onForget(IBinder token);
+
+        private static class Token extends Binder implements CloseableToken, Runnable {
+
+            private final Sender mSender;
+            private Preparer mPreparer;
+
+            private Token(Sender sender, CancellationSignal signal) {
+                mSender = sender;
+                mPreparer = new Preparer(sender, signal, this);
+            }
+
+            @Override
+            public void close() {
+                Preparer preparer = mPreparer;
+                mPreparer = null;
+                if (preparer != null) {
+                    preparer.setup();
+                }
+            }
+
+            @Override
+            public void run() {
+                mSender.onForget(this);
+            }
+
+            private static class Preparer implements CancellationSignal.OnCancelListener {
+                private final Sender mSender;
+                private final CancellationSignal mSignal;
+                private final Token mToken;
+
+                private Preparer(Sender sender, CancellationSignal signal, Token token) {
+                    mSender = sender;
+                    mSignal = signal;
+                    mToken = token;
+                }
+
+                void setup() {
+                    sCleaner.register(this, mToken);
+                    mSignal.setOnCancelListener(this);
+                }
+
+                @Override
+                public void onCancel() {
+                    try {
+                        mSender.onCancel(mToken);
+                    } finally {
+                        // Make sure we dispatch onCancel before the cleaner can run.
+                        Reference.reachabilityFence(this);
+                    }
+                }
+            }
+        }
+
+        /**
+         * A {@link #beam}ed {@link CancellationSignal} ready for sending over Binder.
+         *
+         * MUST be closed <em>after</em> it is sent over binder, ideally through try-with-resources.
+         */
+        public interface CloseableToken extends IBinder, AutoCloseable {
+            @Override
+            void close(); // No throws
+        }
+    }
+
+    /** The receiving side of a {@link CancellationSignalBeamer}. */
+    public static class Receiver implements IBinder.DeathRecipient {
+        private final HashMap<IBinder, CancellationSignal> mTokenMap = new HashMap<>();
+        private final boolean mCancelOnSenderDeath;
+
+        /**
+         * Constructs a new {@code Receiver}.
+         *
+         * @param cancelOnSenderDeath if true, {@link CancellationSignal}s obtained from
+         *   {@link #unbeam} are automatically {@link #cancel}led if the sender token
+         *   {@link Binder#linkToDeath dies}; otherwise they are simnply dropped. Note: if the
+         *   sending process drops all references to the {@link CancellationSignal} before
+         *   process death, the cancellation is not guaranteed.
+         */
+        public Receiver(boolean cancelOnSenderDeath) {
+            mCancelOnSenderDeath = cancelOnSenderDeath;
+        }
+
+        /**
+         * Unbeams a token that was obtained via {@link Sender#beam} and turns it back into a
+         * {@link CancellationSignal}.
+         *
+         * A subsequent call to {@link #cancel} with the same token will cancel the returned
+         * {@code CancellationSignal}.
+         *
+         * @param token a token that was obtained from {@link Sender}, possibly in a remote process.
+         * @return a {@link CancellationSignal} linked to the given token.
+         */
+        @Nullable
+        public CancellationSignal unbeam(@Nullable IBinder token) {
+            if (token == null) {
+                return null;
+            }
+            synchronized (this) {
+                CancellationSignal cs = mTokenMap.get(token);
+                if (cs != null) {
+                    return cs;
+                }
+
+                cs = new CancellationSignal();
+                mTokenMap.put(token, cs);
+                try {
+                    token.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    dead(token);
+                }
+                return cs;
+            }
+        }
+
+        /**
+         * Forgets state associated with the given token (if any).
+         *
+         * Subsequent calls to {@link #cancel} or binder death notifications on the token will not
+         * have any effect.
+         *
+         * This MUST be invoked when forwarding {@link Sender#onForget}, otherwise the token and
+         * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+         *
+         * Optionally, the receiving service logic may also invoke this if it can guarantee that
+         * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+         * using the CancellationSignal has been fully completed).
+         *
+         * @param token the token to forget. No-op if {@code null}.
+         */
+        public void forget(@Nullable IBinder token) {
+            synchronized (this) {
+                if (mTokenMap.remove(token) != null) {
+                    token.unlinkToDeath(this, 0);
+                }
+            }
+        }
+
+        /**
+         * Cancels the {@link CancellationSignal} associated with the given token (if any).
+         *
+         * This MUST be invoked when forwarding {@link Sender#onCancel}, otherwise the token and
+         * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+         *
+         * Optionally, the receiving service logic may also invoke this if it can guarantee that
+         * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+         * using the CancellationSignal has been fully completed).
+         *
+         * @param token the token to forget. No-op if {@code null}.
+         */
+        public void cancel(@Nullable IBinder token) {
+            CancellationSignal cs;
+            synchronized (this) {
+                cs = mTokenMap.get(token);
+                if (cs != null) {
+                    forget(token);
+                } else {
+                    return;
+                }
+            }
+            cs.cancel();
+        }
+
+        private void dead(@NonNull IBinder token) {
+            if (mCancelOnSenderDeath) {
+                cancel(token);
+            } else {
+                forget(token);
+            }
+        }
+
+        @Override
+        public void binderDied(@NonNull IBinder who) {
+            dead(who);
+        }
+
+        @Override
+        public void binderDied() {
+            throw new RuntimeException("unreachable");
+        }
+    }
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bbf7f81..de94b86 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1641,6 +1641,7 @@
             DISALLOW_ADD_WIFI_CONFIG,
             DISALLOW_CELLULAR_2G,
             DISALLOW_ULTRA_WIDEBAND_RADIO,
+            DISALLOW_GRANT_ADMIN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserRestrictionKey {}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 54e4909..acb1f5b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -116,6 +116,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * The Settings provider contains global system-level device preferences.
@@ -2978,19 +2979,22 @@
     }
 
     private static final class GenerationTracker {
-        private final MemoryIntArray mArray;
-        private final Runnable mErrorHandler;
+        @NonNull private final String mName;
+        @NonNull private final MemoryIntArray mArray;
+        @NonNull private final Consumer<String> mErrorHandler;
         private final int mIndex;
         private int mCurrentGeneration;
 
-        public GenerationTracker(@NonNull MemoryIntArray array, int index,
-                int generation, Runnable errorHandler) {
+        GenerationTracker(@NonNull String name, @NonNull MemoryIntArray array, int index,
+                int generation, Consumer<String> errorHandler) {
+            mName = name;
             mArray = array;
             mIndex = index;
             mErrorHandler = errorHandler;
             mCurrentGeneration = generation;
         }
 
+        // This method also updates the obsolete generation code stored locally
         public boolean isGenerationChanged() {
             final int currentGeneration = readCurrentGeneration();
             if (currentGeneration >= 0) {
@@ -3011,9 +3015,7 @@
                 return mArray.get(mIndex);
             } catch (IOException e) {
                 Log.e(TAG, "Error getting current generation", e);
-                if (mErrorHandler != null) {
-                    mErrorHandler.run();
-                }
+                mErrorHandler.accept(mName);
             }
             return -1;
         }
@@ -3023,9 +3025,6 @@
                 mArray.close();
             } catch (IOException e) {
                 Log.e(TAG, "Error closing backing array", e);
-                if (mErrorHandler != null) {
-                    mErrorHandler.run();
-                }
             }
         }
     }
@@ -3088,8 +3087,21 @@
         private final ArraySet<String> mAllFields;
         private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk;
 
+        // Mapping from the name of a setting (or the prefix of a namespace) to a generation tracker
         @GuardedBy("this")
-        private GenerationTracker mGenerationTracker;
+        private ArrayMap<String, GenerationTracker> mGenerationTrackers = new ArrayMap<>();
+
+        private Consumer<String> mGenerationTrackerErrorHandler = (String name) -> {
+            synchronized (NameValueCache.this) {
+                Log.e(TAG, "Error accessing generation tracker - removing");
+                final GenerationTracker tracker = mGenerationTrackers.get(name);
+                if (tracker != null) {
+                    tracker.destroy();
+                    mGenerationTrackers.remove(name);
+                }
+                mValues.remove(name);
+            }
+        };
 
         <T extends NameValueTable> NameValueCache(Uri uri, String getCommand,
                 String setCommand, String deleteCommand, ContentProviderHolder providerHolder,
@@ -3178,6 +3190,43 @@
 
         @UnsupportedAppUsage
         public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
+            final boolean isSelf = (userHandle == UserHandle.myUserId());
+            int currentGeneration = -1;
+            boolean needsGenerationTracker = false;
+
+            if (isSelf) {
+                synchronized (NameValueCache.this) {
+                    final GenerationTracker generationTracker = mGenerationTrackers.get(name);
+                    if (generationTracker != null) {
+                        if (generationTracker.isGenerationChanged()) {
+                            if (DEBUG) {
+                                Log.i(TAG, "Generation changed for setting:" + name
+                                        + " type:" + mUri.getPath()
+                                        + " in package:" + cr.getPackageName()
+                                        + " and user:" + userHandle);
+                            }
+                            mValues.remove(name);
+                        } else if (mValues.containsKey(name)) {
+                            if (DEBUG) {
+                                Log.i(TAG, "Cache hit for setting:" + name);
+                            }
+                            return mValues.get(name);
+                        }
+                        currentGeneration = generationTracker.getCurrentGeneration();
+                    } else {
+                        needsGenerationTracker = true;
+                    }
+                }
+            } else {
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "get setting for user " + userHandle
+                            + " by user " + UserHandle.myUserId() + " so skipping cache");
+                }
+            }
+            if (DEBUG) {
+                Log.i(TAG, "Cache miss for setting:" + name);
+            }
+
             // Check if the target settings key is readable. Reject if the caller is not system and
             // is trying to access a settings key defined in the Settings.Secure, Settings.System or
             // Settings.Global and is not annotated as @Readable.
@@ -3211,31 +3260,6 @@
                 }
             }
 
-            final boolean isSelf = (userHandle == UserHandle.myUserId());
-            int currentGeneration = -1;
-            if (isSelf) {
-                synchronized (NameValueCache.this) {
-                    if (mGenerationTracker != null) {
-                        if (mGenerationTracker.isGenerationChanged()) {
-                            if (DEBUG) {
-                                Log.i(TAG, "Generation changed for type:"
-                                        + mUri.getPath() + " in package:"
-                                        + cr.getPackageName() +" and user:" + userHandle);
-                            }
-                            mValues.clear();
-                        } else if (mValues.containsKey(name)) {
-                            return mValues.get(name);
-                        }
-                        if (mGenerationTracker != null) {
-                            currentGeneration = mGenerationTracker.getCurrentGeneration();
-                        }
-                    }
-                }
-            } else {
-                if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userHandle
-                        + " by user " + UserHandle.myUserId() + " so skipping cache");
-            }
-
             IContentProvider cp = mProviderHolder.getProvider(cr);
 
             // Try the fast path first, not using query().  If this
@@ -3244,24 +3268,17 @@
             // interface.
             if (mCallGetCommand != null) {
                 try {
-                    Bundle args = null;
+                    Bundle args = new Bundle();
                     if (!isSelf) {
-                        args = new Bundle();
                         args.putInt(CALL_METHOD_USER_KEY, userHandle);
                     }
-                    boolean needsGenerationTracker = false;
-                    synchronized (NameValueCache.this) {
-                        if (isSelf && mGenerationTracker == null) {
-                            needsGenerationTracker = true;
-                            if (args == null) {
-                                args = new Bundle();
-                            }
-                            args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
-                            if (DEBUG) {
-                                Log.i(TAG, "Requested generation tracker for type: "+ mUri.getPath()
-                                        + " in package:" + cr.getPackageName() +" and user:"
-                                        + userHandle);
-                            }
+                    if (needsGenerationTracker) {
+                        args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
+                        if (DEBUG) {
+                            Log.i(TAG, "Requested generation tracker for setting:" + name
+                                    + " type:" + mUri.getPath()
+                                    + " in package:" + cr.getPackageName()
+                                    + " and user:" + userHandle);
                         }
                     }
                     Bundle b;
@@ -3298,33 +3315,24 @@
                                         final int generation = b.getInt(
                                                 CALL_METHOD_GENERATION_KEY, 0);
                                         if (DEBUG) {
-                                            Log.i(TAG, "Received generation tracker for type:"
-                                                    + mUri.getPath() + " in package:"
-                                                    + cr.getPackageName() + " and user:"
-                                                    + userHandle + " with index:" + index);
+                                            Log.i(TAG, "Received generation tracker for setting:"
+                                                    + name
+                                                    + " type:" + mUri.getPath()
+                                                    + " in package:" + cr.getPackageName()
+                                                    + " and user:" + userHandle
+                                                    + " with index:" + index);
                                         }
-                                        if (mGenerationTracker != null) {
-                                            mGenerationTracker.destroy();
-                                        }
-                                        mGenerationTracker = new GenerationTracker(array, index,
-                                                generation, () -> {
-                                            synchronized (NameValueCache.this) {
-                                                Log.e(TAG, "Error accessing generation"
-                                                        + " tracker - removing");
-                                                if (mGenerationTracker != null) {
-                                                    GenerationTracker generationTracker =
-                                                            mGenerationTracker;
-                                                    mGenerationTracker = null;
-                                                    generationTracker.destroy();
-                                                    mValues.clear();
-                                                }
-                                            }
-                                        });
+                                        mGenerationTrackers.put(name, new GenerationTracker(name,
+                                                array, index, generation,
+                                                mGenerationTrackerErrorHandler));
                                         currentGeneration = generation;
                                     }
                                 }
-                                if (mGenerationTracker != null && currentGeneration ==
-                                        mGenerationTracker.getCurrentGeneration()) {
+                                if (mGenerationTrackers.get(name) != null && currentGeneration
+                                        == mGenerationTrackers.get(name).getCurrentGeneration()) {
+                                    if (DEBUG) {
+                                        Log.i(TAG, "Updating cache for setting:" + name);
+                                    }
                                     mValues.put(name, value);
                                 }
                             }
@@ -3367,15 +3375,14 @@
 
                 String value = c.moveToNext() ? c.getString(0) : null;
                 synchronized (NameValueCache.this) {
-                    if (mGenerationTracker != null
-                            && currentGeneration == mGenerationTracker.getCurrentGeneration()) {
+                    if (mGenerationTrackers.get(name) != null && currentGeneration
+                            == mGenerationTrackers.get(name).getCurrentGeneration()) {
+                        if (DEBUG) {
+                            Log.i(TAG, "Updating cache for setting:" + name + " using query");
+                        }
                         mValues.put(name, value);
                     }
                 }
-                if (LOCAL_LOGV) {
-                    Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " +
-                            name + " = " + (value == null ? "(null)" : value));
-                }
                 return value;
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
@@ -3409,18 +3416,29 @@
             Config.enforceReadPermission(namespace);
             ArrayMap<String, String> keyValues = new ArrayMap<>();
             int currentGeneration = -1;
+            boolean needsGenerationTracker = false;
 
             synchronized (NameValueCache.this) {
-                if (mGenerationTracker != null) {
-                    if (mGenerationTracker.isGenerationChanged()) {
+                final GenerationTracker generationTracker = mGenerationTrackers.get(prefix);
+                if (generationTracker != null) {
+                    if (generationTracker.isGenerationChanged()) {
                         if (DEBUG) {
-                            Log.i(TAG, "Generation changed for type:" + mUri.getPath()
+                            Log.i(TAG, "Generation changed for prefix:" + prefix
+                                    + " type:" + mUri.getPath()
                                     + " in package:" + cr.getPackageName());
                         }
-                        mValues.clear();
+                        for (int i = 0; i < mValues.size(); ++i) {
+                            String key = mValues.keyAt(i);
+                            if (key.startsWith(prefix)) {
+                                mValues.remove(key);
+                            }
+                        }
                     } else {
                         boolean prefixCached = mValues.containsKey(prefix);
                         if (prefixCached) {
+                            if (DEBUG) {
+                                Log.i(TAG, "Cache hit for prefix:" + prefix);
+                            }
                             if (!names.isEmpty()) {
                                 for (String name : names) {
                                     if (mValues.containsKey(name)) {
@@ -3440,9 +3458,9 @@
                             return keyValues;
                         }
                     }
-                    if (mGenerationTracker != null) {
-                        currentGeneration = mGenerationTracker.getCurrentGeneration();
-                    }
+                    currentGeneration = generationTracker.getCurrentGeneration();
+                } else {
+                    needsGenerationTracker = true;
                 }
             }
 
@@ -3450,20 +3468,20 @@
                 // No list command specified, return empty map
                 return keyValues;
             }
+            if (DEBUG) {
+                Log.i(TAG, "Cache miss for prefix:" + prefix);
+            }
             IContentProvider cp = mProviderHolder.getProvider(cr);
 
             try {
                 Bundle args = new Bundle();
                 args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
-                boolean needsGenerationTracker = false;
-                synchronized (NameValueCache.this) {
-                    if (mGenerationTracker == null) {
-                        needsGenerationTracker = true;
-                        args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
-                        if (DEBUG) {
-                            Log.i(TAG, "Requested generation tracker for type: "
-                                    + mUri.getPath() + " in package:" + cr.getPackageName());
-                        }
+                if (needsGenerationTracker) {
+                    args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
+                    if (DEBUG) {
+                        Log.i(TAG, "Requested generation tracker for prefix:" + prefix
+                                + " type: " + mUri.getPath()
+                                + " in package:" + cr.getPackageName());
                     }
                 }
 
@@ -3516,32 +3534,22 @@
                             final int generation = b.getInt(
                                     CALL_METHOD_GENERATION_KEY, 0);
                             if (DEBUG) {
-                                Log.i(TAG, "Received generation tracker for type:"
-                                        + mUri.getPath() + " in package:"
-                                        + cr.getPackageName() + " with index:" + index);
+                                Log.i(TAG, "Received generation tracker for prefix:" + prefix
+                                        + " type:" + mUri.getPath()
+                                        + " in package:" + cr.getPackageName()
+                                        + " with index:" + index);
                             }
-                            if (mGenerationTracker != null) {
-                                mGenerationTracker.destroy();
-                            }
-                            mGenerationTracker = new GenerationTracker(array, index,
-                                    generation, () -> {
-                                synchronized (NameValueCache.this) {
-                                    Log.e(TAG, "Error accessing generation tracker"
-                                            + " - removing");
-                                    if (mGenerationTracker != null) {
-                                        GenerationTracker generationTracker =
-                                                mGenerationTracker;
-                                        mGenerationTracker = null;
-                                        generationTracker.destroy();
-                                        mValues.clear();
-                                    }
-                                }
-                            });
+                            mGenerationTrackers.put(prefix,
+                                    new GenerationTracker(prefix, array, index, generation,
+                                            mGenerationTrackerErrorHandler));
                             currentGeneration = generation;
                         }
                     }
-                    if (mGenerationTracker != null && currentGeneration
-                            == mGenerationTracker.getCurrentGeneration()) {
+                    if (mGenerationTrackers.get(prefix) != null && currentGeneration
+                            == mGenerationTrackers.get(prefix).getCurrentGeneration()) {
+                        if (DEBUG) {
+                            Log.i(TAG, "Updating cache for prefix:" + prefix);
+                        }
                         // cache the complete list of flags for the namespace
                         mValues.putAll(flagsToValues);
                         // Adding the prefix as a signal that the prefix is cached.
@@ -3557,11 +3565,11 @@
 
         public void clearGenerationTrackerForTest() {
             synchronized (NameValueCache.this) {
-                if (mGenerationTracker != null) {
-                    mGenerationTracker.destroy();
+                for (int i = 0; i < mGenerationTrackers.size(); i++) {
+                    mGenerationTrackers.valueAt(i).destroy();
                 }
+                mGenerationTrackers.clear();
                 mValues.clear();
-                mGenerationTracker = null;
             }
         }
     }
diff --git a/core/java/android/service/credentials/BeginGetCredentialOption.java b/core/java/android/service/credentials/BeginGetCredentialOption.java
index 81b9f22..1ad0424 100644
--- a/core/java/android/service/credentials/BeginGetCredentialOption.java
+++ b/core/java/android/service/credentials/BeginGetCredentialOption.java
@@ -116,12 +116,12 @@
      * @param id the unique id associated with this option
      * @param type               the requested credential type
      * @param candidateQueryData the request candidateQueryData
-     * @throws IllegalArgumentException If type is empty.
+     * @throws IllegalArgumentException If id or type is empty.
      */
     public BeginGetCredentialOption(
             @NonNull String id, @NonNull String type,
             @NonNull Bundle candidateQueryData) {
-        mId = id;
+        mId = Preconditions.checkStringNotEmpty(id, "id must not be empty");
         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
         Bundle bundle = new Bundle();
         bundle.putAll(candidateQueryData);
diff --git a/core/java/android/service/credentials/CallingAppInfo.java b/core/java/android/service/credentials/CallingAppInfo.java
index fba91d4..e755581 100644
--- a/core/java/android/service/credentials/CallingAppInfo.java
+++ b/core/java/android/service/credentials/CallingAppInfo.java
@@ -17,6 +17,7 @@
 package android.service.credentials;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.SigningInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -32,6 +33,8 @@
 public final class CallingAppInfo implements Parcelable {
     @NonNull private final String mPackageName;
     @NonNull private final SigningInfo mSigningInfo;
+    @Nullable
+    private final String mOrigin;
 
     /**
      * Constructs a new instance.
@@ -41,14 +44,31 @@
      */
     public CallingAppInfo(@NonNull String packageName,
             @NonNull SigningInfo signingInfo) {
+        this(packageName, signingInfo, /*origin=*/ null);
+    }
+
+    /**
+     * Constructs a new instance.
+     *
+     * @param packageName - the package name of the calling app
+     * @param signingInfo - the signing info on the calling app
+     * @param origin - the origin that the calling app wants to use when making request on behalf of
+     *               other
+     * @throws IllegalArgumentException If {@code packageName} is null or empty.
+     * @throws NullPointerException If {@code signingInfo} is null.
+     */
+    public CallingAppInfo(@NonNull String packageName,
+            @NonNull SigningInfo signingInfo, @Nullable String origin) {
         mPackageName = Preconditions.checkStringNotEmpty(packageName, "package name"
                 + "must not be null or empty");
         mSigningInfo = Objects.requireNonNull(signingInfo);
+        mOrigin = origin;
     }
 
     private CallingAppInfo(@NonNull Parcel in) {
         mPackageName = in.readString8();
         mSigningInfo = in.readTypedObject(SigningInfo.CREATOR);
+        mOrigin = in.readString8();
     }
 
     public static final @NonNull Creator<CallingAppInfo> CREATOR = new Creator<CallingAppInfo>() {
@@ -76,6 +96,22 @@
         return mSigningInfo;
     }
 
+    /**
+     * Returns the origin of the calling app if set otherwise returns null.
+     * This value is set only if the origin is different than that of the calling app,
+     * and should be expected from privileged callers(browsers) only when making request on behalf
+     * of other applications.
+     *
+     * Android system makes sure that only applications that poses the permission
+     * {@link android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on
+     * the incoming {@link android.credentials.GetCredentialRequest} or
+     * {@link android.credentials.CreateCredentialRequest}.
+     */
+    @Nullable
+    public String getOrigin() {
+        return mOrigin;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -85,6 +121,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString8(mPackageName);
         dest.writeTypedObject(mSigningInfo, flags);
+        dest.writeString8(mOrigin);
     }
 
     @Override
@@ -97,6 +134,7 @@
         } else {
             builder.append(", mSigningInfo: null");
         }
+        builder.append(",mOrigin: " + mOrigin);
         builder.append(" }");
         return builder.toString();
     }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index eb9901a..5c2b389 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -953,7 +953,7 @@
 
     private static Uri safeUri(TypedXmlPullParser parser, String att) {
         final String val = parser.getAttributeValue(null, att);
-        if (TextUtils.isEmpty(val)) return null;
+        if (val == null) return null;
         return Uri.parse(val);
     }
 
diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java
index 466bc05..644a2bf 100644
--- a/core/java/android/service/voice/AbstractDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -77,6 +77,13 @@
         mIsDetectorActive = new AtomicBoolean(true);
     }
 
+    boolean isSameToken(IBinder token) {
+        if (token == null) {
+            return false;
+        }
+        return mToken == token;
+    }
+
     /**
      * Method to be called for the detector to ready/register itself with underlying system
      * services.
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 50ac1f3..b1dc686 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1707,6 +1707,13 @@
         }
     }
 
+    void onDetectorRemoteException() {
+        Message.obtain(mHandler, MSG_DETECTION_ERROR,
+                new HotwordDetectionServiceFailure(
+                        HotwordDetectionServiceFailure.ERROR_CODE_REMOTE_EXCEPTION,
+                        "Detector remote exception occurs")).sendToTarget();
+    }
+
     class MyHandler extends Handler {
         MyHandler(@NonNull Looper looper) {
             super(looper);
diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl
index 6a54606..491056e 100644
--- a/core/java/android/service/voice/IVoiceInteractionService.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionService.aidl
@@ -32,4 +32,5 @@
      in IVoiceActionCheckCallback callback);
     void prepareToShowSession(in Bundle args, int flags);
     void showSessionFailed(in Bundle args);
+    void detectorRemoteExceptionOccurred(in IBinder token, int detectorType);
 }
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index d4b6f3b..767fe37 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -77,6 +77,13 @@
                 DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
     }
 
+    void onDetectorRemoteException() {
+        Binder.withCleanCallingIdentity(() -> mExecutor.execute(() ->
+                mCallback.onFailure(new HotwordDetectionServiceFailure(
+                HotwordDetectionServiceFailure.ERROR_CODE_REMOTE_EXCEPTION,
+                "Detector remote exception occurs"))));
+    }
+
     @RequiresPermission(RECORD_AUDIO)
     @Override
     public boolean startRecognition() throws IllegalDetectorStateException {
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index a684e41..fcc64b0 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -36,8 +36,8 @@
 import android.content.Intent;
 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
 import android.hardware.soundtrigger.SoundTrigger;
-import android.media.voice.KeyphraseModelManager;
 import android.media.permission.Identity;
+import android.media.voice.KeyphraseModelManager;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -180,6 +180,14 @@
                     VoiceInteractionService::onShowSessionFailed,
                     VoiceInteractionService.this, args));
         }
+
+        @Override
+        public void detectorRemoteExceptionOccurred(@NonNull IBinder token, int detectorType) {
+            Log.d(TAG, "detectorRemoteExceptionOccurred");
+            Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+                    VoiceInteractionService::onDetectorRemoteException,
+                    VoiceInteractionService.this, token, detectorType));
+        }
     };
 
     IVoiceInteractionManagerService mSystemService;
@@ -192,6 +200,28 @@
 
     private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
 
+
+    private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) {
+        Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString(
+                detectorType));
+        mActiveDetectors.forEach(detector -> {
+            // TODO: handle normal detector, VQD
+            if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP
+                    && detector instanceof AlwaysOnHotwordDetector) {
+                AlwaysOnHotwordDetector alwaysOnDetector = (AlwaysOnHotwordDetector) detector;
+                if (alwaysOnDetector.isSameToken(token)) {
+                    alwaysOnDetector.onDetectorRemoteException();
+                }
+            } else if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE
+                    && detector instanceof SoftwareHotwordDetector) {
+                SoftwareHotwordDetector softwareDetector = (SoftwareHotwordDetector) detector;
+                if (softwareDetector.isSameToken(token)) {
+                    softwareDetector.onDetectorRemoteException();
+                }
+            }
+        });
+    }
+
     /**
      * Called when a user has activated an affordance to launch voice assist from the Keyguard.
      *
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 12cd523..f53abce 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2198,6 +2198,11 @@
                 }
                 mCreated = false;
             }
+
+            if (mSurfaceControl != null) {
+                mSurfaceControl.release();
+                mSurfaceControl = null;
+            }
         }
 
         private final DisplayListener mDisplayListener = new DisplayListener() {
diff --git a/core/java/android/telephony/CellBroadcastIntents.java b/core/java/android/telephony/CellBroadcastIntents.java
index c3ca286..b9ad773 100644
--- a/core/java/android/telephony/CellBroadcastIntents.java
+++ b/core/java/android/telephony/CellBroadcastIntents.java
@@ -104,7 +104,7 @@
      * Put the phone ID and sub ID into an intent as extras.
      */
     private static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) {
-        int subId = getSubIdForPhone(context, phoneId);
+        int subId = SubscriptionManager.getSubscriptionId(phoneId);
         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
             intent.putExtra("subscription", subId);
             intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
@@ -112,22 +112,4 @@
         intent.putExtra("phone", phoneId);
         intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
     }
-
-    /**
-     * Get the subscription ID for a phone ID, or INVALID_SUBSCRIPTION_ID if the phone does not
-     * have an active sub
-     * @param phoneId the phoneId to use
-     * @return the associated sub id
-     */
-    private static int getSubIdForPhone(Context context, int phoneId) {
-        SubscriptionManager subMan =
-                (SubscriptionManager) context.getSystemService(
-                        Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-        int[] subIds = subMan.getSubscriptionIds(phoneId);
-        if (subIds != null) {
-            return subIds[0];
-        } else {
-            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        }
-    }
 }
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 98c0d7f..5aa0f59 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -477,7 +477,14 @@
         return mTimeResult;
     }
 
-    /** Clears the last received NTP. Intended for use during tests. */
+    /** Sets the last received NTP time. Intended for use during tests. */
+    public void setCachedTimeResult(TimeResult timeResult) {
+        synchronized (this) {
+            mTimeResult = timeResult;
+        }
+    }
+
+    /** Clears the last received NTP time. Intended for use during tests. */
     public void clearCachedTimeResult() {
         synchronized (this) {
             mTimeResult = null;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 20be9d6..5476088 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1261,20 +1261,17 @@
 
     /**
      * @hide
-     * Returns the display's HDR supported types.
+     * Returns the current mode's supported HDR types.
      *
      * @see #isHdr()
-     * @see HdrCapabilities#getSupportedHdrTypes()
+     * @see Mode#getSupportedHdrTypes()
      */
     @TestApi
     @NonNull
     public int[] getReportedHdrTypes() {
         synchronized (mLock) {
             updateDisplayInfoLocked();
-            if (mDisplayInfo.hdrCapabilities == null) {
-                return new int[0];
-            }
-            return mDisplayInfo.hdrCapabilities.getSupportedHdrTypes();
+            return mDisplayInfo.getMode().getSupportedHdrTypes();
         }
     }
 
diff --git a/core/java/android/view/HandwritingDelegateConfiguration.java b/core/java/android/view/HandwritingDelegateConfiguration.java
deleted file mode 100644
index 719c614..0000000
--- a/core/java/android/view/HandwritingDelegateConfiguration.java
+++ /dev/null
@@ -1,74 +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 android.view;
-
-import android.annotation.IdRes;
-import android.annotation.NonNull;
-
-/**
- * Configuration for a view to act as a handwriting initiation delegate. This allows handwriting
- * mode for a delegator editor view to be initiated by stylus movement on the delegate view.
- *
- * <p>If a stylus {@link MotionEvent} occurs within the delegate view's bounds, the callback
- * returned by {@link #getInitiationCallback()} will be called. The callback implementation is
- * expected to show and focus the delegator editor view. If a view with identifier matching {@link
- * #getDelegatorViewId()} creates an input connection while the same stylus {@link MotionEvent}
- * sequence is ongoing, handwriting mode will be initiated for that view.
- *
- * <p>A common use case is a custom view which looks like a text editor but does not actually
- * support text editing itself, and clicking on the custom view causes an EditText to be shown. To
- * support handwriting initiation in this case, {@link View#setHandwritingDelegateConfiguration} can
- * be called on the custom view to configure it as a delegate, and set the EditText as the delegator
- * by passing the EditText's identifier as the {@code delegatorViewId}. The {@code
- * initiationCallback} implementation is typically the same as the click listener implementation
- * which shows the EditText.
- */
-public class HandwritingDelegateConfiguration {
-    @IdRes private final int mDelegatorViewId;
-    @NonNull private final Runnable mInitiationCallback;
-
-    /**
-     * Constructs a HandwritingDelegateConfiguration instance.
-     *
-     * @param delegatorViewId identifier of the delegator editor view for which handwriting mode
-     *     should be initiated
-     * @param initiationCallback callback called when a stylus {@link MotionEvent} occurs within
-     *     this view's bounds. This will be called from the UI thread.
-     */
-    public HandwritingDelegateConfiguration(
-            @IdRes int delegatorViewId, @NonNull Runnable initiationCallback) {
-        mDelegatorViewId = delegatorViewId;
-        mInitiationCallback = initiationCallback;
-    }
-
-    /**
-     * Returns the identifier of the delegator editor view for which handwriting mode should be
-     * initiated.
-     */
-    public int getDelegatorViewId() {
-        return mDelegatorViewId;
-    }
-
-    /**
-     * Returns the callback which should be called when a stylus {@link MotionEvent} occurs within
-     * the delegate view's bounds. The callback should only be called from the UI thread.
-     */
-    @NonNull
-    public Runnable getInitiationCallback() {
-        return mInitiationCallback;
-    }
-}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 8d221ab..98f61a3 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -16,7 +16,6 @@
 
 package android.view;
 
-import android.annotation.IdRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -172,15 +171,11 @@
                     if (candidateView != null) {
                         if (candidateView == getConnectedView()) {
                             startHandwriting(candidateView);
-                        } else if (candidateView.getHandwritingDelegateConfiguration() != null) {
-                            mState.mDelegatorViewId =
-                                    candidateView
-                                            .getHandwritingDelegateConfiguration()
-                                            .getDelegatorViewId();
-                            candidateView
-                                    .getHandwritingDelegateConfiguration()
-                                    .getInitiationCallback()
-                                    .run();
+                        } else if (candidateView.getHandwritingDelegatorCallback() != null) {
+                            mImm.prepareStylusHandwritingDelegation(
+                                    candidateView,
+                                    candidateView.getAllowedHandwritingDelegatePackageName());
+                            candidateView.getHandwritingDelegatorCallback().run();
                         } else {
                             if (candidateView.getRevealOnFocusHint()) {
                                 candidateView.setRevealOnFocusHint(false);
@@ -227,6 +222,9 @@
         } else {
             mConnectedView = new WeakReference<>(view);
             mConnectionCount = 1;
+            if (view.isHandwritingDelegate() && tryAcceptStylusHandwritingDelegation(view)) {
+                return;
+            }
             if (mState != null && mState.mShouldInitHandwriting) {
                 tryStartHandwriting();
             }
@@ -279,17 +277,15 @@
         }
 
         final Rect handwritingArea = getViewHandwritingArea(connectedView);
-        if ((mState.mDelegatorViewId != View.NO_ID
-                        && mState.mDelegatorViewId == connectedView.getId())
-                || isInHandwritingArea(
-                        handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
+        if (isInHandwritingArea(
+                handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
             startHandwriting(connectedView);
         } else {
             mState.mShouldInitHandwriting = false;
         }
     }
 
-    /** For test only. */
+    /** Starts a stylus handwriting session for the view. */
     @VisibleForTesting
     public void startHandwriting(@NonNull View view) {
         mImm.startStylusHandwriting(view);
@@ -298,6 +294,23 @@
     }
 
     /**
+     * Starts a stylus handwriting session for the delegate view, if {@link
+     * InputMethodManager#prepareStylusHandwritingDelegation} was previously called.
+     */
+    @VisibleForTesting
+    public boolean tryAcceptStylusHandwritingDelegation(@NonNull View view) {
+        if (mImm.acceptStylusHandwritingDelegation(
+                view, view.getAllowedHandwritingDelegatorPackageName())) {
+            if (mState != null) {
+                mState.mHasInitiatedHandwriting = true;
+                mState.mShouldInitHandwriting = false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Notify that the handwriting area for the given view might be updated.
      * @param view the view whose handwriting area might be updated.
      */
@@ -542,13 +555,6 @@
          * built InputConnection.
          */
         private boolean mExceedHandwritingSlop;
-        /**
-         * If the current ongoing stylus MotionEvent sequence started over a handwriting initiation
-         * delegate view, then this is the view identifier of the corresponding delegator view. If
-         * the delegator view creates an input connection while the MotionEvent sequence is still
-         * ongoing, then handwriting mode will be initiated for the delegator view.
-         */
-        @IdRes private int mDelegatorViewId = View.NO_ID;
 
         /** The pointer id of the stylus pointer that is being tracked. */
         private final int mStylusPointerId;
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 17ab83c..e02e600 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -133,7 +133,7 @@
         return mVisibleFrame == null || !mVisibleFrame.isEmpty();
     }
 
-    public boolean getInsetsRoundedCornerFrame() {
+    public boolean insetsRoundedCornerFrame() {
         return mInsetsRoundedCornerFrame;
     }
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index ba7d823..997e4a5 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -275,7 +275,7 @@
         final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame);
         for (int i = mSources.size() - 1; i >= 0; i--) {
             final InsetsSource source = mSources.valueAt(i);
-            if (source.getInsetsRoundedCornerFrame()) {
+            if (source.insetsRoundedCornerFrame()) {
                 final Insets insets = source.calculateInsets(roundedCornerFrame, false);
                 roundedCornerFrame.inset(insets);
             }
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 1a5613e..ab81345 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1352,6 +1352,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private int mSource;
     private int mDisplayId = INVALID_DISPLAY;
+    // NOTE: mHmac is private and not used in this class, but it's used on native side / parcel.
     private @Nullable byte[] mHmac;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int mMetaState;
@@ -1377,7 +1378,7 @@
      */
     private long mEventTime;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private String mCharacters;
+    private @Nullable String mCharacters;
 
     public interface Callback {
         /**
@@ -1441,7 +1442,11 @@
     private static native int nativeKeyCodeFromString(String keyCode);
     private static native int nativeNextId();
 
-    private KeyEvent() {}
+    private KeyEvent() {
+        this(/* downTime= */ 0, /* eventTime= */ 0, /* action= */ 0, /* code= */0, /* repeat= */ 0,
+                /* metaState= */ 0, /* deviceId= */ 0, /* scancode= */ 0, /* flags= */ 0,
+                /* source= */ 0, /* characters= */ null);
+    }
 
     /**
      * Create a new key event.
@@ -1451,11 +1456,9 @@
      * @param code The key code.
      */
     public KeyEvent(int action, int code) {
-        mId = nativeNextId();
-        mAction = action;
-        mKeyCode = code;
-        mRepeatCount = 0;
-        mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+        this(/* downTime= */ 0, /* eventTime= */ 0, action, code, /* repeat= */ 0,
+                /* metaState= */ 0, /* deviceId= */ KeyCharacterMap.VIRTUAL_KEYBOARD,
+                /* scancode= */ 0, /* flags= */ 0, /* source= */ 0, /* characters= */ null);
     }
 
     /**
@@ -1473,13 +1476,9 @@
      */
     public KeyEvent(long downTime, long eventTime, int action,
                     int code, int repeat) {
-        mId = nativeNextId();
-        mDownTime = TimeUnit.NANOSECONDS.convert(downTime, TimeUnit.MILLISECONDS);
-        mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
-        mAction = action;
-        mKeyCode = code;
-        mRepeatCount = repeat;
-        mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+        this(downTime, eventTime, action, code, repeat, /* metaState= */ 0,
+                KeyCharacterMap.VIRTUAL_KEYBOARD, /* scancode= */ 0, /* flags= */ 0,
+                /* source= */ 0, /* characters= */ null);
     }
 
     /**
@@ -1498,14 +1497,8 @@
      */
     public KeyEvent(long downTime, long eventTime, int action,
                     int code, int repeat, int metaState) {
-        mId = nativeNextId();
-        mDownTime = TimeUnit.NANOSECONDS.convert(downTime, TimeUnit.MILLISECONDS);
-        mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
-        mAction = action;
-        mKeyCode = code;
-        mRepeatCount = repeat;
-        mMetaState = metaState;
-        mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+        this(downTime, eventTime, action, code, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD,
+                /* scancode= */ 0, /* flags= */ 0, /* source= */ 0, /* characters= */ null);
     }
 
     /**
@@ -1527,15 +1520,8 @@
     public KeyEvent(long downTime, long eventTime, int action,
                     int code, int repeat, int metaState,
                     int deviceId, int scancode) {
-        mId = nativeNextId();
-        mDownTime = TimeUnit.NANOSECONDS.convert(downTime, TimeUnit.MILLISECONDS);
-        mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
-        mAction = action;
-        mKeyCode = code;
-        mRepeatCount = repeat;
-        mMetaState = metaState;
-        mDeviceId = deviceId;
-        mScanCode = scancode;
+        this(downTime, eventTime, action, code, repeat, metaState, deviceId, scancode,
+                /* flags= */ 0, /* source= */ 0, /* characters= */ null);
     }
 
     /**
@@ -1558,16 +1544,8 @@
     public KeyEvent(long downTime, long eventTime, int action,
                     int code, int repeat, int metaState,
                     int deviceId, int scancode, int flags) {
-        mId = nativeNextId();
-        mDownTime = TimeUnit.NANOSECONDS.convert(downTime, TimeUnit.MILLISECONDS);
-        mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
-        mAction = action;
-        mKeyCode = code;
-        mRepeatCount = repeat;
-        mMetaState = metaState;
-        mDeviceId = deviceId;
-        mScanCode = scancode;
-        mFlags = flags;
+        this(downTime, eventTime, action, code, repeat, metaState, deviceId, scancode, flags,
+                /* source= */ 0, /* characters= */ null);
     }
 
     /**
@@ -1591,6 +1569,14 @@
     public KeyEvent(long downTime, long eventTime, int action,
                     int code, int repeat, int metaState,
                     int deviceId, int scancode, int flags, int source) {
+        this(downTime, eventTime, action, code, repeat, metaState, deviceId, scancode, flags,
+                source, /* characters= */ null);
+    }
+
+    private KeyEvent(long downTime, long eventTime, int action, int code, int repeat, int metaState,
+            int deviceId, int scancode, int flags, int source,  @Nullable String characters) {
+        // NOTE: this is the canonical constructor, other constructors that takes KeyEvent
+        // attributes should call it
         mId = nativeNextId();
         mDownTime = TimeUnit.NANOSECONDS.convert(downTime, TimeUnit.MILLISECONDS);
         mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
@@ -1602,6 +1588,7 @@
         mScanCode = scancode;
         mFlags = flags;
         mSource = source;
+        mCharacters = characters;
     }
 
     /**
@@ -1617,36 +1604,18 @@
      * @param flags The flags for this key event
      */
     public KeyEvent(long time, String characters, int deviceId, int flags) {
-        mId = nativeNextId();
-        mDownTime = TimeUnit.NANOSECONDS.convert(time, TimeUnit.MILLISECONDS);
-        mEventTime = TimeUnit.NANOSECONDS.convert(time, TimeUnit.MILLISECONDS);
-        mCharacters = characters;
-        mAction = ACTION_MULTIPLE;
-        mKeyCode = KEYCODE_UNKNOWN;
-        mRepeatCount = 0;
-        mDeviceId = deviceId;
-        mFlags = flags;
-        mSource = InputDevice.SOURCE_KEYBOARD;
+        this(/* downTime= */ time, /* eventTime= */ time, ACTION_MULTIPLE, KEYCODE_UNKNOWN,
+                /* repeat= */ 0, /* metaState= */ 0, deviceId, /* scancode= */ 0, flags,
+                /* source= */ InputDevice.SOURCE_KEYBOARD, characters);
     }
 
     /**
      * Make an exact copy of an existing key event.
      */
     public KeyEvent(KeyEvent origEvent) {
-        mId = origEvent.mId;
-        mDownTime = origEvent.mDownTime;
-        mEventTime = origEvent.mEventTime;
-        mAction = origEvent.mAction;
-        mKeyCode = origEvent.mKeyCode;
-        mRepeatCount = origEvent.mRepeatCount;
-        mMetaState = origEvent.mMetaState;
-        mDeviceId = origEvent.mDeviceId;
-        mSource = origEvent.mSource;
-        mDisplayId = origEvent.mDisplayId;
-        mHmac = origEvent.mHmac == null ? null : origEvent.mHmac.clone();
-        mScanCode = origEvent.mScanCode;
-        mFlags = origEvent.mFlags;
-        mCharacters = origEvent.mCharacters;
+        this(origEvent, origEvent.mId, origEvent.mEventTime, origEvent.mAction,
+                origEvent.mRepeatCount, origEvent.mHmac == null ? null : origEvent.mHmac.clone(),
+                origEvent.mCharacters);
     }
 
     /**
@@ -1662,20 +1631,30 @@
      */
     @Deprecated
     public KeyEvent(KeyEvent origEvent, long eventTime, int newRepeat) {
-        mId = nativeNextId();  // Not an exact copy so assign a new ID.
+        // Not an exact copy so assign a new ID.
+        // Don't copy HMAC, it will be invalid because eventTime is changing
+        this(origEvent, nativeNextId(),
+                TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS), origEvent.mAction,
+                newRepeat, /* hmac= */ null, origEvent.mCharacters);
+    }
+
+    // This is the canonical constructor that should be called for constructors that take a KeyEvent
+    private KeyEvent(KeyEvent origEvent, int id, long eventTime, int action, int newRepeat,
+            @Nullable byte[] hmac, @Nullable String characters) {
+        mId = id;
         mDownTime = origEvent.mDownTime;
-        mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
-        mAction = origEvent.mAction;
+        mEventTime = eventTime;
+        mAction = action;
         mKeyCode = origEvent.mKeyCode;
         mRepeatCount = newRepeat;
         mMetaState = origEvent.mMetaState;
         mDeviceId = origEvent.mDeviceId;
         mSource = origEvent.mSource;
         mDisplayId = origEvent.mDisplayId;
-        mHmac = null; // Don't copy HMAC, it will be invalid because eventTime is changing
+        mHmac = hmac;
         mScanCode = origEvent.mScanCode;
         mFlags = origEvent.mFlags;
-        mCharacters = origEvent.mCharacters;
+        mCharacters = characters;
     }
 
     private static KeyEvent obtain() {
@@ -1857,21 +1836,11 @@
      * @param action The new action code of the event.
      */
     private KeyEvent(KeyEvent origEvent, int action) {
-        mId = nativeNextId();  // Not an exact copy so assign a new ID.
-        mDownTime = origEvent.mDownTime;
-        mEventTime = origEvent.mEventTime;
-        mAction = action;
-        mKeyCode = origEvent.mKeyCode;
-        mRepeatCount = origEvent.mRepeatCount;
-        mMetaState = origEvent.mMetaState;
-        mDeviceId = origEvent.mDeviceId;
-        mSource = origEvent.mSource;
-        mDisplayId = origEvent.mDisplayId;
-        mHmac = null; // Don't copy the hmac, it will be invalid since action is changing
-        mScanCode = origEvent.mScanCode;
-        mFlags = origEvent.mFlags;
-        // Don't copy mCharacters, since one way or the other we'll lose it
-        // when changing the action.
+        // Not an exact copy so assign a new ID
+        // Don't copy the hmac, it will be invalid since action is changing
+        // Don't copy mCharacters, since one way or the other we'll lose it when changing action.
+        this(origEvent, nativeNextId(), origEvent.mEventTime, action, origEvent.mRepeatCount,
+                /* hmac= */ null, /* characters= */ null);
     }
 
     /**
@@ -3219,6 +3188,8 @@
     }
 
     private KeyEvent(Parcel in) {
+        // NOTE: ideally this constructor should call the canonical one, but that would require
+        // changing the order the fields are written to the parcel, which could break native code
         mId = in.readInt();
         mDeviceId = in.readInt();
         mSource = in.readInt();
diff --git a/core/java/android/view/TaskTransitionSpec.java b/core/java/android/view/TaskTransitionSpec.java
index 5f498a1..9a2d3ba 100644
--- a/core/java/android/view/TaskTransitionSpec.java
+++ b/core/java/android/view/TaskTransitionSpec.java
@@ -18,9 +18,6 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.ArraySet;
-
-import java.util.Set;
 
 /**
  * Holds information about how to execute task transition animations.
@@ -36,32 +33,12 @@
      */
     public final int backgroundColor;
 
-    /**
-     * TEMPORARY FIELD (b/202383002)
-     * TODO: Remove once we use surfaceflinger rounded corners on tasks rather than taskbar overlays
-     *  or when shell transitions are fully enabled
-     *
-     * A set of {@InsetsState.InternalInsetsType}s we want to use as the source to set the bounds
-     * of the task during the animation. Used to make sure that task animate above the taskbar.
-     * Will also be used to crop to the frame size of the inset source to the inset size to prevent
-     * the taskbar rounded corners overlay from being invisible during task transition animation.
-     */
-    public final Set<Integer> animationBoundInsets;
-
-    public TaskTransitionSpec(
-            int backgroundColor, Set<Integer> animationBoundInsets) {
+    public TaskTransitionSpec(int backgroundColor) {
         this.backgroundColor = backgroundColor;
-        this.animationBoundInsets = animationBoundInsets;
     }
 
     public TaskTransitionSpec(Parcel in) {
         this.backgroundColor = in.readInt();
-
-        int animationBoundInsetsSize = in.readInt();
-        this.animationBoundInsets = new ArraySet<>(animationBoundInsetsSize);
-        for (int i = 0; i < animationBoundInsetsSize; i++) {
-            this.animationBoundInsets.add(in.readInt());
-        }
     }
 
     @Override
@@ -72,11 +49,6 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(backgroundColor);
-
-        dest.writeInt(animationBoundInsets.size());
-        for (int insetType : animationBoundInsets) {
-            dest.writeInt(insetType);
-        }
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<TaskTransitionSpec>
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7d18bf0..fee2051 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5101,12 +5101,13 @@
      */
     private boolean mHoveringTouchDelegate = false;
 
-    /**
-     * Configuration for this view to act as a handwriting initiation delegate. This allows
-     * handwriting mode for a delegator editor view to be initiated by stylus movement on this
-     * delegate view.
-     */
-    private HandwritingDelegateConfiguration mHandwritingDelegateConfiguration;
+    // These two fields are set if the view is a handwriting delegator.
+    private Runnable mHandwritingDelegatorCallback;
+    private String mAllowedHandwritingDelegatePackageName;
+
+    // These two fields are set if the view is a handwriting delegate.
+    private boolean mIsHandwritingDelegate;
+    private String mAllowedHandwritingDelegatorPackageName;
 
     /**
      * Solid color to use as a background when creating the drawing cache. Enables
@@ -12410,27 +12411,168 @@
     }
 
     /**
-     * Configures this view to act as a handwriting initiation delegate. This allows handwriting
-     * mode for a delegator editor view to be initiated by stylus movement on this delegate view.
+     * Sets a callback which should be called when a stylus {@link MotionEvent} occurs within this
+     * view's bounds. The callback will be called from the UI thread.
+     *
+     * <p>Setting a callback allows this view to act as a handwriting delegator, so that handwriting
+     * mode for a delegate editor view can be initiated by stylus movement on this delegator view.
+     * The callback implementation is expected to show and focus the delegate editor view. If a view
+     * which returns {@code true} for {@link #isHandwritingDelegate()} creates an input connection
+     * while the same stylus {@link MotionEvent} sequence is ongoing, handwriting mode will be
+     * initiated for that view.
+     *
+     * <p>A common use case is a custom view which looks like a text editor but does not actually
+     * support text editing itself, and clicking on the custom view causes an EditText to be shown.
+     * To support handwriting initiation in this case, this method can be called on the custom view
+     * to configure it as a delegator. The EditText should call {@link #setIsHandwritingDelegate} to
+     * set it as a delegate. The {@code callback} implementation is typically the same as the click
+     * listener implementation which shows the EditText.
      *
      * <p>If {@code null} is passed, this view will no longer act as a handwriting initiation
-     * delegate.
+     * delegator.
+     *
+     * @param callback a callback which should be called when a stylus {@link MotionEvent} occurs
+     *     within this view's bounds
      */
-    public void setHandwritingDelegateConfiguration(
-            @Nullable HandwritingDelegateConfiguration configuration) {
-        mHandwritingDelegateConfiguration = configuration;
-        if (configuration != null) {
+    public void setHandwritingDelegatorCallback(@Nullable Runnable callback) {
+        mHandwritingDelegatorCallback = callback;
+        if (callback != null) {
+            // By default, the delegate must be from the same package as the delegator view.
+            mAllowedHandwritingDelegatePackageName = mContext.getOpPackageName();
             setHandwritingArea(new Rect(0, 0, getWidth(), getHeight()));
+        } else {
+            mAllowedHandwritingDelegatePackageName = null;
         }
     }
 
     /**
-     * If this view has been configured as a handwriting initiation delegate, returns the delegate
-     * configuration.
+     * Returns the callback set by {@link #setHandwritingDelegatorCallback} which should be called
+     * when a stylus {@link MotionEvent} occurs within this view's bounds. The callback should only
+     * be called from the UI thread.
      */
     @Nullable
-    public HandwritingDelegateConfiguration getHandwritingDelegateConfiguration() {
-        return mHandwritingDelegateConfiguration;
+    public Runnable getHandwritingDelegatorCallback() {
+        return mHandwritingDelegatorCallback;
+    }
+
+    /**
+     * Specifies that this view may act as a handwriting initiation delegator for a delegate editor
+     * view from the specified package. If this method is not called, delegators may only be used to
+     * initiate handwriting mode for a delegate editor view from the same package as the delegator
+     * view. This method allows specifying a different trusted package which may contain a delegate
+     * editor view linked to this delegator view. This should be called after {@link
+     * #setHandwritingDelegatorCallback}.
+     *
+     * <p>If this method is called on the delegator view, then {@link
+     * #setAllowedHandwritingDelegatorPackage} should also be called on the delegate editor view.
+     *
+     * <p>For example, to configure a delegator view in package 1:
+     *
+     * <pre>
+     * delegatorView.setHandwritingDelegatorCallback(callback);
+     * delegatorView.setAllowedHandwritingDelegatePackage(package2);</pre>
+     *
+     * Then to configure the corresponding delegate editor view in package 2:
+     *
+     * <pre>
+     * delegateEditorView.setIsHandwritingDelegate(true);
+     * delegateEditorView.setAllowedHandwritingDelegatorPackage(package1);</pre>
+     *
+     * @param allowedPackageName the package name of a delegate editor view linked to this delegator
+     *     view
+     * @throws IllegalStateException If the view has not been configured as a handwriting delegator
+     *     using {@link #setHandwritingDelegatorCallback}.
+     */
+    public void setAllowedHandwritingDelegatePackage(@NonNull String allowedPackageName) {
+        if (mHandwritingDelegatorCallback == null) {
+            throw new IllegalStateException("This view is not a handwriting delegator.");
+        }
+        mAllowedHandwritingDelegatePackageName = allowedPackageName;
+    }
+
+    /**
+     * Returns the allowed package for delegate editor views for which this view may act as a
+     * handwriting delegator. If {@link #setAllowedHandwritingDelegatePackage} has not been called,
+     * this will return this view's package name, since by default delegators may only be used to
+     * initiate handwriting mode for a delegate editor view from the same package as the delegator
+     * view. This will return a different allowed package if set by {@link
+     * #setAllowedHandwritingDelegatePackage}.
+     *
+     * @throws IllegalStateException If the view has not been configured as a handwriting delegator
+     *     using {@link #setHandwritingDelegatorCallback}.
+     */
+    @NonNull
+    public String getAllowedHandwritingDelegatePackageName() {
+        if (mHandwritingDelegatorCallback == null) {
+            throw new IllegalStateException("This view is not a handwriting delegator.");
+        }
+        return mAllowedHandwritingDelegatePackageName;
+    }
+
+    /**
+     * Sets this view to be a handwriting delegate. If a delegate view creates an input connection
+     * while a stylus {@link MotionEvent} sequence from a delegator view is ongoing, handwriting
+     * mode will be initiated for the delegate view.
+     *
+     * @param isHandwritingDelegate whether this view is a handwriting initiation delegate
+     * @see #setHandwritingDelegatorCallback(Runnable)
+     */
+    public void setIsHandwritingDelegate(boolean isHandwritingDelegate) {
+        mIsHandwritingDelegate = isHandwritingDelegate;
+        if (mIsHandwritingDelegate) {
+            // By default, the delegator must be from the same package as the delegate view.
+            mAllowedHandwritingDelegatorPackageName = mContext.getOpPackageName();
+        } else {
+            mAllowedHandwritingDelegatePackageName = null;
+        }
+    }
+
+    /**
+     * Returns whether this view has been set as a handwriting delegate by {@link
+     * #setIsHandwritingDelegate}.
+     */
+    public boolean isHandwritingDelegate() {
+        return mIsHandwritingDelegate;
+    }
+
+    /**
+     * Specifies that a view from the specified package may act as a handwriting delegator for this
+     * delegate editor view. If this method is not called, only views from the same package as the
+     * delegate editor view may act as a handwriting delegator. This method allows specifying a
+     * different trusted package which may contain a delegator view linked to this delegate editor
+     * view. This should be called after {@link #setIsHandwritingDelegate}.
+     *
+     * <p>If this method is called on the delegate editor view, then {@link
+     * #setAllowedHandwritingDelegatePackage} should also be called on the delegator view.
+     *
+     * @param allowedPackageName the package name of a delegator view linked to this delegate editor
+     *     view
+     * @throws IllegalStateException If the view has not been configured as a handwriting delegate
+     *     using {@link #setIsHandwritingDelegate}.
+     */
+    public void setAllowedHandwritingDelegatorPackage(@NonNull String allowedPackageName) {
+        if (!mIsHandwritingDelegate) {
+            throw new IllegalStateException("This view is not a handwriting delegate.");
+        }
+        mAllowedHandwritingDelegatorPackageName = allowedPackageName;
+    }
+
+    /**
+     * Returns the allowed package for views which may act as a handwriting delegator for this
+     * delegate editor view. If {@link #setAllowedHandwritingDelegatorPackage} has not been called,
+     * this will return this view's package name, since by default only views from the same package
+     * as the delegator editor view may act as a handwriting delegator. This will return a different
+     * allowed package if set by {@link #setAllowedHandwritingDelegatorPackage}.
+     *
+     * @throws IllegalStateException If the view has not been configured as a handwriting delegate
+     *     using {@link #setIsHandwritingDelegate}.
+     */
+    @NonNull
+    public String getAllowedHandwritingDelegatorPackageName() {
+        if (!mIsHandwritingDelegate) {
+            throw new IllegalStateException("This view is not a handwriting delegate.");
+        }
+        return mAllowedHandwritingDelegatorPackageName;
     }
 
     /**
@@ -24474,7 +24616,7 @@
             }
         }
         rebuildOutline();
-        if (onCheckIsTextEditor() || mHandwritingDelegateConfiguration != null) {
+        if (onCheckIsTextEditor() || mHandwritingDelegatorCallback != null) {
             setHandwritingArea(new Rect(0, 0, newWidth, newHeight));
         }
     }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e9b3e28..f430ec3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1136,7 +1136,7 @@
         // Make sure to report the completion of draw for relaunch with preserved window.
         reportNextDraw("rebuilt");
         // Make sure to resume this root view when relaunching its host activity which was stopped.
-        if (mStopped && getHostVisibility() != View.GONE) {
+        if (mStopped) {
             setWindowStopped(false);
         }
     }
diff --git a/core/java/android/view/accessibility/DirectAccessibilityConnection.java b/core/java/android/view/accessibility/DirectAccessibilityConnection.java
index 8a3bb6f..25f5b8c 100644
--- a/core/java/android/view/accessibility/DirectAccessibilityConnection.java
+++ b/core/java/android/view/accessibility/DirectAccessibilityConnection.java
@@ -50,13 +50,13 @@
 class DirectAccessibilityConnection extends IAccessibilityServiceConnection.Default {
     private final IAccessibilityInteractionConnection mAccessibilityInteractionConnection;
     private final AccessibilityManager mAccessibilityManager;
+    private final int mMyProcessId;
 
     // Fetch all views, but do not use prefetching/cache since this "connection" does not
     // receive cache invalidation events (as it is not linked to an AccessibilityService).
     private static final int FETCH_FLAGS =
             AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
                     | AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
-    private static final int PID = Process.myPid();
     private static final Region INTERACTIVE_REGION = null;
 
     DirectAccessibilityConnection(
@@ -64,6 +64,7 @@
             AccessibilityManager accessibilityManager) {
         mAccessibilityInteractionConnection = accessibilityInteractionConnection;
         mAccessibilityManager = accessibilityManager;
+        mMyProcessId = Process.myPid();
     }
 
     @Override
@@ -74,8 +75,9 @@
         IAccessibilityManager.WindowTransformationSpec spec =
                 mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
         mAccessibilityInteractionConnection.findAccessibilityNodeInfoByAccessibilityId(
-                accessibilityNodeId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID,
-                threadId, spec.magnificationSpec, spec.transformationMatrix, arguments);
+                accessibilityNodeId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS,
+                mMyProcessId, threadId, spec.magnificationSpec, spec.transformationMatrix,
+                arguments);
         return new String[0];
     }
 
@@ -87,8 +89,8 @@
         IAccessibilityManager.WindowTransformationSpec spec =
                 mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
         mAccessibilityInteractionConnection.findAccessibilityNodeInfosByText(accessibilityNodeId,
-                text, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
-                spec.magnificationSpec, spec.transformationMatrix);
+                text, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, mMyProcessId,
+                threadId, spec.magnificationSpec, spec.transformationMatrix);
         return new String[0];
     }
 
@@ -100,8 +102,8 @@
         IAccessibilityManager.WindowTransformationSpec spec =
                 mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
         mAccessibilityInteractionConnection.findAccessibilityNodeInfosByViewId(accessibilityNodeId,
-                viewId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
-                spec.magnificationSpec, spec.transformationMatrix);
+                viewId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, mMyProcessId,
+                threadId, spec.magnificationSpec, spec.transformationMatrix);
         return new String[0];
     }
 
@@ -112,7 +114,7 @@
         IAccessibilityManager.WindowTransformationSpec spec =
                 mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
         mAccessibilityInteractionConnection.findFocus(accessibilityNodeId, focusType,
-                INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+                INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, mMyProcessId, threadId,
                 spec.magnificationSpec, spec.transformationMatrix);
         return new String[0];
     }
@@ -124,7 +126,7 @@
         IAccessibilityManager.WindowTransformationSpec spec =
                 mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
         mAccessibilityInteractionConnection.focusSearch(accessibilityNodeId, direction,
-                INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+                INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, mMyProcessId, threadId,
                 spec.magnificationSpec, spec.transformationMatrix);
         return new String[0];
     }
@@ -135,7 +137,7 @@
             IAccessibilityInteractionConnectionCallback callback, long threadId)
             throws RemoteException {
         mAccessibilityInteractionConnection.performAccessibilityAction(accessibilityNodeId, action,
-                arguments, interactionId, callback, FETCH_FLAGS, PID, threadId);
+                arguments, interactionId, callback, FETCH_FLAGS, mMyProcessId, threadId);
         return true;
     }
 }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bdc7333..aef0e65 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -899,9 +899,10 @@
 
         // 3. Get the activity names substring between the indexes
         final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1;
-        if (activityStringStartIndex < firstNextSemicolonIndex) {
+        if (activityStringStartIndex >= firstNextSemicolonIndex) {
             Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly "
                     + "formatted");
+            return;
         }
         final String activitySubstring =
                 denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex);
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 96602619..d84acc0 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -25,6 +25,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.content.Context;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -33,6 +34,7 @@
 import android.window.ImeOnBackInvokedDispatcher;
 
 import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.IInputMethodClient;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -40,7 +42,6 @@
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
-import com.android.internal.view.IImeTracker;
 import com.android.internal.view.IInputMethodManager;
 
 import java.util.ArrayList;
@@ -505,6 +506,40 @@
     }
 
     @AnyThread
+    static void prepareStylusHandwritingDelegation(
+            @NonNull IInputMethodClient client,
+            @NonNull String delegatePackageName,
+            @NonNull String delegatorPackageName) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.prepareStylusHandwritingDelegation(
+                    client, delegatePackageName, delegatorPackageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static boolean acceptStylusHandwritingDelegation(
+            @NonNull IInputMethodClient client,
+            @NonNull String delegatePackageName,
+            @NonNull String delegatorPackageName) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return false;
+        }
+        try {
+            return service.acceptStylusHandwritingDelegation(
+                    client, delegatePackageName, delegatorPackageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     static boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
         final IInputMethodManager service = getService();
@@ -547,51 +582,57 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onRequestShow */
     @AnyThread
-    @Nullable
-    static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
+    @NonNull
+    static ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
-            return null;
+            // Create token with "fake" binder if the service was not found.
+            return new ImeTracker.Token(new Binder(), tag);
         }
         try {
-            return service.onRequestShow(uid, origin, reason);
+            return service.onRequestShow(tag, uid, origin, reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onRequestHide */
     @AnyThread
-    @Nullable
-    static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
+    @NonNull
+    static ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
-            return null;
+            // Create token with "fake" binder if the service was not found.
+            return new ImeTracker.Token(new Binder(), tag);
         }
         try {
-            return service.onRequestHide(uid, origin, reason);
+            return service.onRequestHide(tag, uid, origin, reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onProgress */
     @AnyThread
-    static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    static void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
         }
         try {
-            service.onProgress(statsToken, phase);
+            service.onProgress(binder, phase);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onFailed */
     @AnyThread
-    static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    static void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -603,8 +644,9 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onCancelled */
     @AnyThread
-    static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    static void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -616,8 +658,9 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onShown */
     @AnyThread
-    static void onShown(@NonNull IBinder statsToken) {
+    static void onShown(@NonNull ImeTracker.Token statsToken) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -629,8 +672,9 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#onHidden */
     @AnyThread
-    static void onHidden(@NonNull IBinder statsToken) {
+    static void onHidden(@NonNull ImeTracker.Token statsToken) {
         final IImeTracker service = getImeTrackerService();
         if (service == null) {
             return;
@@ -642,6 +686,7 @@
         }
     }
 
+    /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */
     @AnyThread
     @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     static boolean hasPendingImeVisibilityRequests() {
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index e5a99ff..f0d1019 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -26,7 +26,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -47,7 +46,7 @@
 import java.util.Arrays;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
 
 /** @hide */
@@ -321,9 +320,8 @@
     /**
      * Creates an IME show request tracking token.
      *
-     * @param component the component name where the IME show request was created,
-     *                  or {@code null} otherwise
-     *                  (defaulting to {@link ActivityThread#currentProcessName()}).
+     * @param component the name of the component that created the IME request, or {@code null}
+     *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME show request.
      * @param reason the reason why the IME show request was created.
@@ -337,9 +335,8 @@
     /**
      * Creates an IME hide request tracking token.
      *
-     * @param component the component name where the IME hide request was created,
-     *                  or {@code null} otherwise
-     *                  (defaulting to {@link ActivityThread#currentProcessName()}).
+     * @param component the name of the component that created the IME request, or {@code null}
+     *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME hide request.
      * @param reason the reason why the IME hide request was created.
@@ -435,8 +432,7 @@
             mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false);
             // Update logging flag dynamically.
             SystemProperties.addChangeCallback(() ->
-                    mLogProgress =
-                            SystemProperties.getBoolean("persist.debug.imetracker", false));
+                    mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false));
         }
 
         /** Whether progress should be logged. */
@@ -446,10 +442,9 @@
         @Override
         public Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
                 @SoftInputShowHideReason int reason) {
-            IBinder binder = IInputMethodManagerGlobalInvoker.onRequestShow(uid, origin, reason);
-            if (binder == null) binder = new Binder();
-
-            final Token token = Token.build(binder, component);
+            final var tag = getTag(component);
+            final var token = IInputMethodManagerGlobalInvoker.onRequestShow(tag, uid, origin,
+                    reason);
 
             Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
@@ -461,10 +456,9 @@
         @Override
         public Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
                 @SoftInputShowHideReason int reason) {
-            IBinder binder = IInputMethodManagerGlobalInvoker.onRequestHide(uid, origin, reason);
-            if (binder == null) binder = new Binder();
-
-            final Token token = Token.build(binder, component);
+            final var tag = getTag(component);
+            final var token = IInputMethodManagerGlobalInvoker.onRequestHide(tag, uid, origin,
+                    reason);
 
             Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
@@ -485,7 +479,7 @@
         @Override
         public void onFailed(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onFailed(token.mBinder, phase);
+            IInputMethodManagerGlobalInvoker.onFailed(token, phase);
 
             Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase));
         }
@@ -499,7 +493,7 @@
         @Override
         public void onCancelled(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onCancelled(token.mBinder, phase);
+            IInputMethodManagerGlobalInvoker.onCancelled(token, phase);
 
             Log.i(TAG, token.mTag + ": onCancelled at " + Debug.phaseToString(phase));
         }
@@ -507,7 +501,7 @@
         @Override
         public void onShown(@Nullable Token token) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onShown(token.mBinder);
+            IInputMethodManagerGlobalInvoker.onShown(token);
 
             Log.i(TAG, token.mTag + ": onShown");
         }
@@ -515,10 +509,24 @@
         @Override
         public void onHidden(@Nullable Token token) {
             if (token == null) return;
-            IInputMethodManagerGlobalInvoker.onHidden(token.mBinder);
+            IInputMethodManagerGlobalInvoker.onHidden(token);
 
             Log.i(TAG, token.mTag + ": onHidden");
         }
+
+        /**
+         * Returns a logging tag using the given component name.
+         *
+         * @param component the name of the component that created the IME request, or {@code null}
+         *                  otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
+         */
+        @NonNull
+        private String getTag(@Nullable String component) {
+            if (component == null) {
+                component = ActivityThread.currentProcessName();
+            }
+            return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
+        }
     };
 
     /** The singleton IME tracker instance for instrumenting jank metrics. */
@@ -528,28 +536,31 @@
     ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker();
 
     /** A token that tracks the progress of an IME request. */
-    class Token implements Parcelable {
+    final class Token implements Parcelable {
 
+        /** The binder used to identify this token. */
         @NonNull
-        public final IBinder mBinder;
+        private final IBinder mBinder;
 
+        /** Logging tag, of the shape "component:random_hexadecimal". */
         @NonNull
         private final String mTag;
 
-        @NonNull
-        private static Token build(@NonNull IBinder binder, @Nullable String component) {
-            if (component == null) component = ActivityThread.currentProcessName();
-            final String tag = component + ":" + Integer.toHexString((new Random().nextInt()));
-
-            return new Token(binder, tag);
-        }
-
-        private Token(@NonNull IBinder binder, @NonNull String tag) {
+        public Token(@NonNull IBinder binder, @NonNull String tag) {
             mBinder = binder;
             mTag = tag;
         }
 
-        /** Returns the {@link Token#mTag} */
+        private Token(@NonNull Parcel in) {
+            mBinder = in.readStrongBinder();
+            mTag = in.readString8();
+        }
+
+        @NonNull
+        public IBinder getBinder() {
+            return mBinder;
+        }
+
         @NonNull
         public String getTag() {
             return mTag;
@@ -562,7 +573,7 @@
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeStrongBinder(mBinder);
             dest.writeString8(mTag);
         }
@@ -571,12 +582,11 @@
         public static final Creator<Token> CREATOR = new Creator<>() {
             @NonNull
             @Override
-            public Token createFromParcel(Parcel source) {
-                final IBinder binder = source.readStrongBinder();
-                final String tag = source.readString8();
-                return new Token(binder, tag);
+            public Token createFromParcel(@NonNull Parcel in) {
+                return new Token(in);
             }
 
+            @NonNull
             @Override
             public Token[] newArray(int size) {
                 return new Token[size];
@@ -589,40 +599,50 @@
      *
      * Note: This is held in a separate class so that it only gets initialized when actually needed.
      */
-    class Debug {
+    final class Debug {
 
+        @NonNull
         private static final Map<Integer, String> sTypes =
                 getFieldMapping(ImeTracker.class, "TYPE_");
+        @NonNull
         private static final Map<Integer, String> sStatus =
                 getFieldMapping(ImeTracker.class, "STATUS_");
+        @NonNull
         private static final Map<Integer, String> sOrigins =
                 getFieldMapping(ImeTracker.class, "ORIGIN_");
+        @NonNull
         private static final Map<Integer, String> sPhases =
                 getFieldMapping(ImeTracker.class, "PHASE_");
 
+        @NonNull
         public static String typeToString(@Type int type) {
             return sTypes.getOrDefault(type, "TYPE_" + type);
         }
 
+        @NonNull
         public static String statusToString(@Status int status) {
             return sStatus.getOrDefault(status, "STATUS_" + status);
         }
 
+        @NonNull
         public static String originToString(@Origin int origin) {
             return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
         }
 
+        @NonNull
         public static String phaseToString(@Phase int phase) {
             return sPhases.getOrDefault(phase, "PHASE_" + phase);
         }
 
-        private static Map<Integer, String> getFieldMapping(Class<?> cls, String fieldPrefix) {
+        @NonNull
+        private static Map<Integer, String> getFieldMapping(Class<?> cls,
+                @NonNull String fieldPrefix) {
             return Arrays.stream(cls.getDeclaredFields())
                     .filter(field -> field.getName().startsWith(fieldPrefix))
                     .collect(Collectors.toMap(Debug::getFieldValue, Field::getName));
         }
 
-        private static int getFieldValue(Field field) {
+        private static int getFieldValue(@NonNull Field field) {
             try {
                 return field.getInt(null);
             } catch (IllegalAccessException e) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 642182b..36d2b8a 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -99,6 +99,7 @@
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
+import android.widget.Editor;
 import android.window.ImeOnBackInvokedDispatcher;
 import android.window.WindowOnBackInvokedDispatcher;
 
@@ -1552,11 +1553,7 @@
         if (fallbackContext == null) {
             return false;
         }
-        if (Settings.Global.getInt(fallbackContext.getContentResolver(),
-                Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 0) {
-            if (DEBUG) {
-                Log.d(TAG, "Stylus handwriting is not enabled in settings.");
-            }
+        if (!isStylusHandwritingEnabled(fallbackContext)) {
             return false;
         }
         return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(userId);
@@ -2233,37 +2230,175 @@
      * @see #isStylusHandwritingAvailable()
      */
     public void startStylusHandwriting(@NonNull View view) {
+        startStylusHandwritingInternal(view, null /* delegatorPackageName */);
+    }
+
+    private boolean startStylusHandwritingInternal(
+            @NonNull View view, @Nullable String delegatorPackageName) {
+        Objects.requireNonNull(view);
+
         // Re-dispatch if there is a context mismatch.
         final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
         if (fallbackImm != null) {
-            fallbackImm.startStylusHandwriting(view);
+            fallbackImm.startStylusHandwritingInternal(view, delegatorPackageName);
         }
-        Objects.requireNonNull(view);
 
-        if (Settings.Global.getInt(view.getContext().getContentResolver(),
-                Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 0) {
-            Log.d(TAG, "Ignoring startStylusHandwriting(view) as stylus handwriting is disabled");
-            return;
+        boolean useDelegation = !TextUtils.isEmpty(delegatorPackageName);
+        if (!isStylusHandwritingEnabled(view.getContext())) {
+            Log.w(TAG, "Stylus handwriting pref is disabled. "
+                    + "Ignoring calls to start stylus handwriting.");
+            return false;
         }
 
         checkFocus();
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view)) {
                 Log.w(TAG,
-                        "Ignoring startStylusHandwriting() as view=" + view + " is not served.");
-                return;
+                        "Ignoring startStylusHandwriting as view=" + view + " is not served.");
+                return false;
             }
             if (view.getViewRootImpl() != mCurRootView) {
-                Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus.");
-                return;
+                Log.w(TAG,
+                        "Ignoring startStylusHandwriting: View's window does not have focus.");
+                return false;
             }
-
-            IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient);
-            // TODO(b/210039666): do we need any extra work for supporting non-native
-            //   UI toolkits?
+            if (useDelegation) {
+                return IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegation(
+                        mClient, view.getContext().getOpPackageName(), delegatorPackageName);
+            } else {
+                IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient);
+            }
+            return false;
         }
     }
 
+    private boolean isStylusHandwritingEnabled(@NonNull Context context) {
+        if (Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 0) {
+            Log.d(TAG, "Stylus handwriting pref is disabled.");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Prepares delegation of starting stylus handwriting session to a different editor in same
+     * or different window than the view on which initial handwriting stroke was detected.
+     *
+     * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+     * its {@link InputConnection} is started. Calling this method starts buffering of stylus
+     * motion events until {@link #acceptStylusHandwritingDelegation(View)} is called, at which
+     * point the handwriting session can be started and the buffered stylus motion events will be
+     * delivered to the IME.
+     * e.g. Delegation can be used when initial handwriting stroke is
+     * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
+     * {@link Editor} is on a different window.
+     *
+     * <p> Note: If an actual {@link Editor} capable of {@link InputConnection} is being scribbled
+     * upon using stylus, use {@link #startStylusHandwriting(View)} instead.</p>
+     *
+     * @param delegatorView the view that receives initial stylus stroke and delegates it to the
+     *  actual editor. Its window must {@link View#hasWindowFocus have focus}.
+     * @see #prepareStylusHandwritingDelegation(View, String)
+     * @see #acceptStylusHandwritingDelegation(View)
+     * @see #startStylusHandwriting(View)
+     */
+    public void prepareStylusHandwritingDelegation(@NonNull View delegatorView) {
+        prepareStylusHandwritingDelegation(
+                delegatorView, delegatorView.getContext().getOpPackageName());
+    }
+
+    /**
+     * Prepares delegation of starting stylus handwriting session to a different editor in same or a
+     * different window in a different package than the view on which initial handwriting stroke
+     * was detected.
+     *
+     * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+     * its {@link InputConnection} is started. Calling this method starts buffering of stylus
+     * motion events until {@link #acceptStylusHandwritingDelegation(View, String)} is called, at
+     * which point the handwriting session can be started and the buffered stylus motion events will
+     * be delivered to the IME.
+     * e.g. Delegation can be used when initial handwriting stroke is
+     * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
+     * {@link Editor} is on a different window in the given package.
+     *
+     * <p>Note: If delegator and delegate are in same package use
+     * {@link #prepareStylusHandwritingDelegation(View)} instead.</p>
+     *
+     * @param delegatorView  the view that receives initial stylus stroke and delegates it to the
+     * actual editor. Its window must {@link View#hasWindowFocus have focus}.
+     * @param delegatePackageName package name that contains actual {@link Editor} which should
+     *  start stylus handwriting session by calling {@link #acceptStylusHandwritingDelegation}.
+     * @see #prepareStylusHandwritingDelegation(View)
+     * @see #acceptStylusHandwritingDelegation(View, String)
+     */
+    public void prepareStylusHandwritingDelegation(
+            @NonNull View delegatorView, @NonNull String delegatePackageName) {
+        Objects.requireNonNull(delegatorView);
+        Objects.requireNonNull(delegatePackageName);
+
+        // Re-dispatch if there is a context mismatch.
+        final InputMethodManager fallbackImm =
+                getFallbackInputMethodManagerIfNecessary(delegatorView);
+        if (fallbackImm != null) {
+            fallbackImm.prepareStylusHandwritingDelegation(delegatorView, delegatePackageName);
+        }
+
+        if (!isStylusHandwritingEnabled(delegatorView.getContext())) {
+            Log.w(TAG, "Stylus handwriting pref is disabled. "
+                    + "Ignoring prepareStylusHandwritingDelegation().");
+            return;
+        }
+        IInputMethodManagerGlobalInvoker.prepareStylusHandwritingDelegation(
+                mClient,
+                delegatePackageName,
+                delegatorView.getContext().getOpPackageName());
+    }
+
+    /**
+     * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
+     * initiation delegation was previously requested using
+     * {@link #prepareStylusHandwritingDelegation(View)} from the delegator.
+     *
+     * <p>Note: If delegator and delegate are in different application packages, use
+     * {@link #acceptStylusHandwritingDelegation(View, String)} instead.</p>
+     *
+     * @param delegateView delegate view capable of receiving input via {@link InputConnection}
+     *  on which {@link #startStylusHandwriting(View)} will be called.
+     * @return {@code true} if view belongs to same application package as used in
+     *  {@link #prepareStylusHandwritingDelegation(View)} and handwriting session can start.
+     * @see #acceptStylusHandwritingDelegation(View, String)
+     * @see #prepareStylusHandwritingDelegation(View)
+     */
+    public boolean acceptStylusHandwritingDelegation(@NonNull View delegateView) {
+        return startStylusHandwritingInternal(
+                delegateView, delegateView.getContext().getOpPackageName());
+    }
+
+    /**
+     * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
+     * initiation delegation was previously requested using
+     * {@link #prepareStylusHandwritingDelegation(View, String)} from te delegator and the view
+     * belongs to a specified delegate package.
+     *
+     * <p>Note: If delegator and delegate are in same application package use
+     * {@link #acceptStylusHandwritingDelegation(View)} instead.</p>
+     *
+     * @param delegateView delegate view capable of receiving input via {@link InputConnection}
+     *  on which {@link #startStylusHandwriting(View)} will be called.
+     * @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
+     * @return {@code true} if view belongs to allowed delegate package declared in
+     *  {@link #prepareStylusHandwritingDelegation(View, String)} and handwriting session can start.
+     * @see #prepareStylusHandwritingDelegation(View, String)
+     * @see #acceptStylusHandwritingDelegation(View)
+     */
+    public boolean acceptStylusHandwritingDelegation(
+            @NonNull View delegateView, @NonNull String delegatorPackageName) {
+        Objects.requireNonNull(delegatorPackageName);
+
+        return startStylusHandwritingInternal(delegateView, delegatorPackageName);
+    }
+
     /**
      * This method toggles the input method window display.
      * If the input window is already displayed, it gets hidden.
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b89dd31..9f9a781 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -52,6 +52,7 @@
 import android.graphics.RenderNode;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.LocaleList;
@@ -3238,6 +3239,44 @@
                 .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
 
         mPreserveSelection = true;
+
+        // No-op for the old context menu because it doesn't have icons.
+        adjustIconSpacing(menu);
+    }
+
+    /**
+     * Adjust icon spacing to align the texts.
+     * @hide
+     */
+    @VisibleForTesting
+    public void adjustIconSpacing(ContextMenu menu) {
+        int width = -1;
+        int height = -1;
+        for (int i = 0; i < menu.size(); ++i) {
+            final MenuItem item = menu.getItem(i);
+            final Drawable d = item.getIcon();
+            if (d == null) {
+                continue;
+            }
+
+            width = Math.max(width, d.getIntrinsicWidth());
+            height = Math.max(height, d.getIntrinsicHeight());
+        }
+
+        if (width < 0 || height < 0) {
+            return;  // No menu has icon drawable.
+        }
+
+        GradientDrawable paddingDrawable = new GradientDrawable();
+        paddingDrawable.setSize(width, height);
+
+        for (int i = 0; i < menu.size(); ++i) {
+            final MenuItem item = menu.getItem(i);
+            final Drawable d = item.getIcon();
+            if (d == null) {
+                item.setIcon(paddingDrawable);
+            }
+        }
     }
 
     @Nullable
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index ef25501..b83d1d8 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -650,6 +650,11 @@
          *
          */
         private void setEmptyShortcutTargetIfNeeded() {
+            if (hasFeatureLeanback()) {
+                // Do not disable the default shortcut on TV.
+                return;
+            }
+
             final ContentResolver contentResolver = mContext.getContentResolver();
 
             final String shortcutTargets = Settings.Secure.getStringForUser(contentResolver,
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 586b309..9fae211 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -64,7 +64,7 @@
 
         /** Gating the feature which shows FSI-denied notifications as Sticky HUNs */
         public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
-                devFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
+                releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
 
         /** Gating the ability for users to dismiss ongoing event notifications */
         public static final Flag ALLOW_DISMISS_ONGOING =
diff --git a/core/java/com/android/internal/view/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
similarity index 71%
rename from core/java/com/android/internal/view/IImeTracker.aidl
rename to core/java/com/android/internal/inputmethod/IImeTracker.aidl
index b062ca7..c7418ee 100644
--- a/core/java/com/android/internal/view/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -14,43 +14,45 @@
  * limitations under the License.
  */
 
-package com.android.internal.view;
+package com.android.internal.inputmethod;
 
 import android.view.inputmethod.ImeTracker;
 
 /**
- * Interface to the global Ime tracker, used by all client applications.
+ * Interface to the global IME tracker service, used by all client applications.
  * {@hide}
  */
 interface IImeTracker {
 
     /**
-     * Called when an IME show request is created,
-     * returns a new Binder to be associated with the IME tracking token.
+     * Called when an IME show request is created.
      *
+     * @param tag the logging tag.
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME show request.
      * @param reason the reason why the IME show request was created.
+     * @return A new IME tracking token.
      */
-    IBinder onRequestShow(int uid, int origin, int reason);
+    ImeTracker.Token onRequestShow(String tag, int uid, int origin, int reason);
 
     /**
-     * Called when an IME hide request is created,
-     * returns a new Binder to be associated with the IME tracking token.
+     * Called when an IME hide request is created.
      *
+     * @param tag the logging tag.
      * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME hide request.
      * @param reason the reason why the IME hide request was created.
+     * @return A new IME tracking token.
      */
-    IBinder onRequestHide(int uid, int origin, int reason);
+    ImeTracker.Token onRequestHide(String tag, int uid, int origin, int reason);
 
     /**
      * Called when the IME request progresses to a further phase.
      *
-     * @param statsToken the token tracking the current IME request.
+     * @param binder the binder of token tracking the current IME request.
      * @param phase the new phase the IME request reached.
      */
-    oneway void onProgress(in IBinder statsToken, int phase);
+    oneway void onProgress(in IBinder binder, int phase);
 
     /**
      * Called when the IME request fails.
@@ -58,7 +60,7 @@
      * @param statsToken the token tracking the current IME request.
      * @param phase the phase the IME request failed at.
      */
-    oneway void onFailed(in IBinder statsToken, int phase);
+    oneway void onFailed(in ImeTracker.Token statsToken, int phase);
 
     /**
      * Called when the IME request is cancelled.
@@ -66,21 +68,21 @@
      * @param statsToken the token tracking the current IME request.
      * @param phase the phase the IME request was cancelled at.
      */
-    oneway void onCancelled(in IBinder statsToken, int phase);
+    oneway void onCancelled(in ImeTracker.Token statsToken, int phase);
 
     /**
      * Called when the IME show request is successful.
      *
      * @param statsToken the token tracking the current IME request.
      */
-    oneway void onShown(in IBinder statsToken);
+    oneway void onShown(in ImeTracker.Token statsToken);
 
     /**
      * Called when the IME hide request is successful.
      *
      * @param statsToken the token tracking the current IME request.
      */
-    oneway void onHidden(in IBinder statsToken);
+    oneway void onHidden(in ImeTracker.Token statsToken);
 
     /**
      * Checks whether there are any pending IME visibility requests.
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index f7e1d57..617519b 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -330,7 +330,10 @@
         }
     }
 
-    public BatteryStatsHistory(HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+    public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+        mMaxHistoryFiles = maxHistoryFiles;
+        mMaxHistoryBufferSize = maxHistoryBufferSize;
         mStepDetailsCalculator = stepDetailsCalculator;
         mTracer = new TraceDelegate();
         mClock = clock;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index b529a10..f7c03cd 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -56,6 +56,7 @@
     void showRecentApps(boolean triggeredFromAltTab);
     void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
     void toggleRecentApps();
+    void toggleTaskbar();
     void toggleSplitScreen();
     void preloadRecentApps();
     void cancelPreloadRecentApps();
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 9116cb3..9a4610e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -23,11 +23,11 @@
 import android.view.inputmethod.EditorInfo;
 import android.window.ImeOnBackInvokedDispatcher;
 
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.IInputMethodClient;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 import com.android.internal.inputmethod.InputBindResult;
-import com.android.internal.view.IImeTracker;
 
 /**
  * Public interface to the global input method manager, used by all client
@@ -148,6 +148,15 @@
     /** Start Stylus handwriting session **/
     void startStylusHandwriting(in IInputMethodClient client);
 
+    /** Prepares delegation of starting stylus handwriting session to a different editor **/
+    void prepareStylusHandwritingDelegation(in IInputMethodClient client,
+                in String delegatePackageName,
+                in String delegatorPackageName);
+
+    /** Accepts and starts a stylus handwriting session for the delegate view **/
+    boolean acceptStylusHandwritingDelegation(in IInputMethodClient client,
+                in String delegatePackageName, in String delegatorPackageName);
+
     /** Returns {@code true} if currently selected IME supports Stylus handwriting. */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 16db818..85d1765 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7107,6 +7107,13 @@
          @hide Not for use by third-party applications. -->
     <permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"
         android:protectionLevel="signature|role" />
+
+    <!-- @SystemApi Permission that allows apps to disable the clipboard access notifications.
+         @hide
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION"
+        android:protectionLevel="signature|installer" />
+
     <!-- @hide Permission that suppresses the notification when the clipboard is accessed.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d2ee5de..1bbe8ee 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1714,7 +1714,7 @@
             this type.
         -->
         <flag name="remoteMessaging" value="0x200" />
-        <!-- The system exmpted foreground service use cases.
+        <!-- The system exempted foreground service use cases.
             <p>Requires the app to hold the permission
             {@link android.Manifest.permission#FOREGROUND_SERVICE_SYSTEM_EXEMPTED} in order to use
             this type. Apps are allowed to use this type only in the use cases listed in
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 12eff67..2f94ed7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3065,6 +3065,10 @@
     <string name="config_credentialManagerDialogComponent" translatable="false"
             >com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
 
+    <!-- Name of the broadcast receiver that is used to receive provider change events -->
+    <string name="config_credentialManagerReceiverComponent" translatable="false"
+            >com.android.credentialmanager/com.android.credentialmanager.CredentialProviderReceiver</string>
+
     <!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
     <string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7e89fc8..12646a0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2210,6 +2210,7 @@
   <java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
   <java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
   <java-symbol type="string" name="config_credentialManagerDialogComponent" />
+  <java-symbol type="string" name="config_credentialManagerReceiverComponent" />
   <java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
   <java-symbol type="string" name="config_persistentDataPackageName" />
   <java-symbol type="string" name="config_deviceConfiguratorPackageName" />
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 55ef854..980211f 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -27,6 +27,7 @@
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.util.PollingCheck;
 import android.view.View;
 import android.widget.TextView;
 
@@ -91,6 +92,9 @@
 
         var densityRef = new AtomicReference<Float>();
         scenario.onActivity(activity -> {
+            assertThat(activity.getResources().getConfiguration().fontScale)
+                .isWithin(0.05f)
+                .of(2f);
             densityRef.compareAndSet(null, activity.getResources().getDisplayMetrics().density);
         });
         var density = densityRef.get();
@@ -141,6 +145,15 @@
                     fontScale
             );
         });
+
+        PollingCheck.waitFor(/* timeout= */ 5000, () ->
+                InstrumentationRegistry
+                    .getInstrumentation()
+                    .getContext()
+                    .getResources()
+                    .getConfiguration()
+                    .fontScale == fontScale
+        );
     }
 
     private Matcher<View> withTextSizeInRange(float sizeStartPx, float sizeEndPx) {
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 249e246..ef106bc 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -90,11 +90,7 @@
             }
             .forEach { (table, sp) ->
                 try {
-                    assertWithMessage(
-                        "convertSpToDp(%s) on table: %s",
-                        sp.toString(),
-                        table.toString()
-                    )
+                    assertWithMessage("convertSpToDp(%s) on table: %s", sp, table)
                         .that(table.convertSpToDp(sp))
                         .isFinite()
                 } catch (e: Exception) {
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index 444e9f2..25664fb 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.same;
@@ -102,8 +101,11 @@
         mGetRequest = new GetCredentialRequest.Builder(Bundle.EMPTY).addCredentialOption(
                 new CredentialOption(Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY,
                         Bundle.EMPTY, false)).build();
-        mCreateRequest = new CreateCredentialRequest(Credential.TYPE_PASSWORD_CREDENTIAL,
-                Bundle.EMPTY, Bundle.EMPTY, false, false);
+        mCreateRequest = new CreateCredentialRequest.Builder(Bundle.EMPTY, Bundle.EMPTY)
+                .setType(Credential.TYPE_PASSWORD_CREDENTIAL)
+                .setIsSystemProviderRequired(false)
+                .setAlwaysSendAppInfoToProvider(false)
+                .build();
         mClearRequest = new ClearCredentialStateRequest(Bundle.EMPTY);
 
         final Slice slice = new Slice.Builder(Uri.parse("foo://bar"), null).addText("some text",
@@ -593,15 +595,12 @@
 
     @Test
     public void testRegisterCredentialDescription_nullRequest() {
-        assumeTrue(CredentialManager.isCredentialDescriptionApiEnabled());
         assertThrows(NullPointerException.class,
                 () -> mCredentialManager.registerCredentialDescription(null));
     }
 
     @Test
     public void testRegisterCredentialDescription_success() throws RemoteException {
-        assumeTrue(CredentialManager.isCredentialDescriptionApiEnabled());
-
         mCredentialManager.registerCredentialDescription(mRegisterRequest);
         verify(mMockCredentialManagerService).registerCredentialDescription(same(mRegisterRequest),
                 eq(mPackageName));
@@ -609,16 +608,12 @@
 
     @Test
     public void testUnregisterCredentialDescription_nullRequest() {
-        assumeTrue(CredentialManager.isCredentialDescriptionApiEnabled());
-
         assertThrows(NullPointerException.class,
                 () -> mCredentialManager.unregisterCredentialDescription(null));
     }
 
     @Test
     public void testUnregisterCredentialDescription_success() throws RemoteException {
-        assumeTrue(CredentialManager.isCredentialDescriptionApiEnabled());
-
         mCredentialManager.unregisterCredentialDescription(mUnregisterRequest);
         verify(mMockCredentialManagerService).unregisterCredentialDescription(
                 same(mUnregisterRequest), eq(mPackageName));
diff --git a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
new file mode 100644
index 0000000..42c97f3
--- /dev/null
+++ b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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 android.os;
+
+import static android.os.CancellationSignalBeamer.Sender;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.CancellationSignalBeamer.Receiver;
+import android.util.PollingCheck;
+import android.util.PollingCheck.PollingCheckCondition;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CancellationSignalBeamerTest {
+
+    private CancellationSignal mSenderSignal = new CancellationSignal();
+    private CancellationSignal mReceivedSignal;
+    private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void testBeam_null() {
+        try (var token = mSender.beam(null)) {
+            assertThat(token).isNull();
+            invokeGenericService(token);
+        }
+        assertThat(mReceivedSignal).isNull();
+    }
+
+    @Test
+    public void testBeam_nonNull() {
+        try (var token = mSender.beam(mSenderSignal)) {
+            assertThat(token).isNotNull();
+            invokeGenericService(token);
+        }
+        assertThat(mReceivedSignal).isNotNull();
+    }
+
+    @Test
+    public void testBeam_async() {
+        IBinder outerToken;
+        try (var token = mSender.beam(mSenderSignal)) {
+            assertThat(token).isNotNull();
+            outerToken = token;
+        }
+        invokeGenericService(outerToken);
+        assertThat(mReceivedSignal).isNotNull();
+    }
+
+    @Test
+    public void testCancelOnSentSignal_cancelsReceivedSignal() {
+        try (var token = mSender.beam(mSenderSignal)) {
+            invokeGenericService(token);
+        }
+        mSenderSignal.cancel();
+        assertThat(mReceivedSignal.isCanceled()).isTrue();
+    }
+
+    @Test
+    public void testSendingCancelledSignal_cancelsReceivedSignal() {
+        mSenderSignal.cancel();
+        try (var token = mSender.beam(mSenderSignal)) {
+            invokeGenericService(token);
+        }
+        assertThat(mReceivedSignal.isCanceled()).isTrue();
+    }
+
+    @Test
+    public void testUnbeam_null() {
+        assertThat(mReceiver.unbeam(null)).isNull();
+    }
+
+    @Test
+    public void testForget_null() {
+        mReceiver.forget(null);
+    }
+
+    @Test
+    public void testCancel_null() {
+        mReceiver.cancel(null);
+    }
+
+    @Test
+    public void testForget_withUnknownToken() {
+        mReceiver.forget(new Binder());
+    }
+
+    @Test
+    public void testCancel_withUnknownToken() {
+        mReceiver.cancel(new Binder());
+    }
+
+    @Test
+    public void testBinderDied_withUnknownToken() {
+        mReceiver.binderDied(new Binder());
+    }
+
+    @Test
+    public void testReceiverWithCancelOnSenderDead_cancelsOnSenderDeath() {
+        var receiver = new Receiver(true /* cancelOnSenderDeath */);
+        var token = new Binder();
+        var signal = receiver.unbeam(token);
+        receiver.binderDied(token);
+        assertThat(signal.isCanceled()).isTrue();
+    }
+
+    @Test
+    public void testReceiverWithoutCancelOnSenderDead_doesntCancelOnSenderDeath() {
+        var receiver = new Receiver(false /* cancelOnSenderDeath */);
+        var token = new Binder();
+        var signal = receiver.unbeam(token);
+        receiver.binderDied(token);
+        assertThat(signal.isCanceled()).isFalse();
+    }
+
+    @Test
+    public void testDroppingSentSignal_dropsReceivedSignal() throws Exception {
+        // In a multiprocess scenario, sending token over Binder might leak the token
+        // on both ends if we create a reference cycle. Simulate that worst-case scenario
+        // here by leaking it directly, then test that cleanup of the signals still works.
+        var receivedSignalCleaned = new CountDownLatch(1);
+        var tokenRef = new Object[1];
+        // Reference the cancellation signals in a separate method scope, so we don't
+        // accidentally leak them on the stack / in a register.
+        Runnable r = () -> {
+            try (var token = mSender.beam(mSenderSignal)) {
+                tokenRef[0] = token;
+                invokeGenericService(token);
+            }
+            mSenderSignal = null;
+
+            Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown);
+            mReceivedSignal = null;
+        };
+        r.run();
+
+        waitForWithGc(() -> receivedSignalCleaned.getCount() == 0);
+
+        Reference.reachabilityFence(tokenRef[0]);
+    }
+
+    @Test
+    public void testRepeatedBeaming_doesntLeak() throws Exception {
+        var receivedSignalCleaned = new CountDownLatch(1);
+        var tokenRef = new Object[1];
+        // Reference the cancellation signals in a separate method scope, so we don't
+        // accidentally leak them on the stack / in a register.
+        Runnable r = () -> {
+            try (var token = mSender.beam(mSenderSignal)) {
+                tokenRef[0] = token;
+                invokeGenericService(token);
+            }
+            // Beaming again leaves mReceivedSignal dangling, so it should be collected.
+            mSender.beam(mSenderSignal).close();
+
+            Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown);
+            mReceivedSignal = null;
+        };
+        r.run();
+
+        waitForWithGc(() -> receivedSignalCleaned.getCount() == 0);
+
+        Reference.reachabilityFence(tokenRef[0]);
+    }
+
+    private void waitForWithGc(PollingCheckCondition condition) throws IOException {
+        try {
+            PollingCheck.waitFor(() -> {
+                Runtime.getRuntime().gc();
+                return condition.canProceed();
+            });
+        } catch (AssertionError e) {
+            File heap = new File(mContext.getExternalFilesDir(null), "dump.hprof");
+            Debug.dumpHprofData(heap.getAbsolutePath());
+            throw e;
+        }
+    }
+
+    private void invokeGenericService(IBinder cancellationSignalToken) {
+        mReceivedSignal = mReceiver.unbeam(cancellationSignalToken);
+    }
+
+    private final Sender mSender = new Sender() {
+        @Override
+        public void onCancel(IBinder token) {
+            mReceiver.cancel(token);
+        }
+
+        @Override
+        public void onForget(IBinder token) {
+            mReceiver.forget(token);
+        }
+    };
+
+    private final Receiver mReceiver = new Receiver(false);
+}
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index 2e31bb5..b6fc137 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -32,16 +32,18 @@
 import android.test.mock.MockContentResolver;
 import android.util.MemoryIntArray;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.IOException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -59,42 +61,63 @@
     private static final String NAMESPACE = "namespace";
     private static final String NAMESPACE2 = "namespace2";
 
+    private static final String SETTING = "test_setting";
+    private static final String SETTING2 = "test_setting2";
+
     @Mock
     private IContentProvider mMockIContentProvider;
     @Mock
     private ContentProvider mMockContentProvider;
     private MockContentResolver mMockContentResolver;
-    private MemoryIntArray mCacheGenerationStore;
-    private int mCurrentGeneration = 123;
+    private MemoryIntArray mConfigsCacheGenerationStore;
+    private MemoryIntArray mSettingsCacheGenerationStore;
 
-    private HashMap<String, HashMap<String, String>> mStorage;
+    private HashMap<String, HashMap<String, String>> mConfigsStorage;
+    private HashMap<String, String> mSettingsStorage;
+
 
     @Before
     public void setUp() throws Exception {
         Settings.Config.clearProviderForTest();
+        Settings.Secure.clearProviderForTest();
         MockitoAnnotations.initMocks(this);
         when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
-        mMockContentResolver = new MockContentResolver(InstrumentationRegistry
-                .getInstrumentation().getContext());
+        mMockContentResolver = new MockContentResolver(
+                InstrumentationRegistry.getInstrumentation().getContext());
         mMockContentResolver.addProvider(Settings.Config.CONTENT_URI.getAuthority(),
                 mMockContentProvider);
-        mCacheGenerationStore = new MemoryIntArray(1);
-        mStorage = new HashMap<>();
+        mMockContentResolver.addProvider(Settings.Secure.CONTENT_URI.getAuthority(),
+                mMockContentProvider);
+        mConfigsCacheGenerationStore = new MemoryIntArray(2);
+        mConfigsCacheGenerationStore.set(0, 123);
+        mConfigsCacheGenerationStore.set(1, 456);
+        mSettingsCacheGenerationStore = new MemoryIntArray(2);
+        mSettingsCacheGenerationStore.set(0, 234);
+        mSettingsCacheGenerationStore.set(1, 567);
+        mConfigsStorage = new HashMap<>();
+        mSettingsStorage = new HashMap<>();
 
         // Stores keyValues for a given prefix and increments the generation. (Note that this
         // increments the generation no matter what, it doesn't pay attention to if anything
         // actually changed).
         when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
-                any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class))).thenAnswer(
+                invocationOnMock -> {
                     Bundle incomingBundle = invocationOnMock.getArgument(4);
                     HashMap<String, String> keyValues =
                             (HashMap<String, String>) incomingBundle.getSerializable(
-                                    Settings.CALL_METHOD_FLAGS_KEY);
+                                    Settings.CALL_METHOD_FLAGS_KEY, HashMap.class);
                     String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
-                    mStorage.put(prefix, keyValues);
-                    mCacheGenerationStore.set(0, ++mCurrentGeneration);
-
+                    mConfigsStorage.put(prefix, keyValues);
+                    int currentGeneration;
+                    // Different prefixes have different generation codes
+                    if (prefix.equals(NAMESPACE + "/")) {
+                        currentGeneration = mConfigsCacheGenerationStore.get(0);
+                        mConfigsCacheGenerationStore.set(0, ++currentGeneration);
+                    } else if (prefix.equals(NAMESPACE2 + "/")) {
+                        currentGeneration = mConfigsCacheGenerationStore.get(1);
+                        mConfigsCacheGenerationStore.set(1, ++currentGeneration);
+                    }
                     Bundle result = new Bundle();
                     result.putInt(Settings.KEY_CONFIG_SET_ALL_RETURN,
                             Settings.SET_ALL_RESULT_SUCCESS);
@@ -102,32 +125,87 @@
                 });
 
         // Returns the keyValues corresponding to a namespace, or an empty map if the namespace
-        // doesn't have anything stored for it. Returns the generation key if the caller asked
-        // for one.
+        // doesn't have anything stored for it. Returns the generation key if the map isn't empty
+        // and the caller asked for the generation key.
         when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class))).thenAnswer(
+                invocationOnMock -> {
                     Bundle incomingBundle = invocationOnMock.getArgument(4);
 
                     String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
 
-                    if (!mStorage.containsKey(prefix)) {
-                        mStorage.put(prefix, new HashMap<>());
+                    if (!mConfigsStorage.containsKey(prefix)) {
+                        mConfigsStorage.put(prefix, new HashMap<>());
                     }
-                    HashMap<String, String> keyValues = mStorage.get(prefix);
+                    HashMap<String, String> keyValues = mConfigsStorage.get(prefix);
 
                     Bundle bundle = new Bundle();
                     bundle.putSerializable(Settings.NameValueTable.VALUE, keyValues);
 
-                    if (incomingBundle.containsKey(Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+                    if (!keyValues.isEmpty() && incomingBundle.containsKey(
+                            Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+                        int index = prefix.equals(NAMESPACE + "/") ? 0 : 1;
                         bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
-                                mCacheGenerationStore);
-                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, 0);
+                                mConfigsCacheGenerationStore);
+                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
                         bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
-                                mCacheGenerationStore.get(0));
+                                mConfigsCacheGenerationStore.get(index));
                     }
                     return bundle;
                 });
+
+        // Stores value for a given setting's name and increments the generation. (Note that this
+        // increments the generation no matter what, it doesn't pay attention to if anything
+        // actually changed).
+        when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
+                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class))).thenAnswer(
+                invocationOnMock -> {
+                    Bundle incomingBundle = invocationOnMock.getArgument(4);
+                    String key = invocationOnMock.getArgument(3);
+                    String value = incomingBundle.getString(Settings.NameValueTable.VALUE);
+                    mSettingsStorage.put(key, value);
+                    int currentGeneration;
+                    // Different settings have different generation codes
+                    if (key.equals(SETTING)) {
+                        currentGeneration = mSettingsCacheGenerationStore.get(0);
+                        mSettingsCacheGenerationStore.set(0, ++currentGeneration);
+                    } else if (key.equals(SETTING2)) {
+                        currentGeneration = mSettingsCacheGenerationStore.get(1);
+                        mSettingsCacheGenerationStore.set(1, ++currentGeneration);
+                    }
+                    return null;
+                });
+
+        // Returns the value corresponding to a setting, or null if the setting
+        // doesn't have a value stored for it. Returns the generation key if the value isn't null
+        // and the caller asked for the generation key.
+        when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class))).thenAnswer(
+                invocationOnMock -> {
+                    Bundle incomingBundle = invocationOnMock.getArgument(4);
+                    String key = invocationOnMock.getArgument(3);
+                    String value = mSettingsStorage.get(key);
+
+                    Bundle bundle = new Bundle();
+                    bundle.putSerializable(Settings.NameValueTable.VALUE, value);
+
+                    if (value != null && incomingBundle.containsKey(
+                            Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+                        int index = key.equals(SETTING) ? 0 : 1;
+                        bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
+                                mSettingsCacheGenerationStore);
+                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
+                        bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
+                                mSettingsCacheGenerationStore.get(index));
+                    }
+                    return bundle;
+                });
+    }
+
+    @After
+    public void cleanUp() throws IOException {
+        mConfigsStorage.clear();
+        mSettingsStorage.clear();
     }
 
     @Test
@@ -135,16 +213,13 @@
         HashMap<String, String> keyValues = new HashMap<>();
         keyValues.put("a", "b");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
-        verify(mMockIContentProvider).call(any(), any(),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
-                any(), any(Bundle.class));
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
 
         Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE,
-                Collections.emptyList());
-        verify(mMockIContentProvider).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class));
+                NAMESPACE, Collections.emptyList());
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
         assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
 
         Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
@@ -156,14 +231,12 @@
         keyValues.put("a", "c");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
-                any(), any(Bundle.class));
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
 
         Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
                 NAMESPACE, Collections.emptyList());
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class));
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
         assertThat(returnedValues2).containsExactlyEntriesIn(keyValues);
 
         Map<String, String> cachedKeyValues2 = Settings.Config.getStrings(mMockContentResolver,
@@ -177,36 +250,31 @@
         HashMap<String, String> keyValues = new HashMap<>();
         keyValues.put("a", "b");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
-        verify(mMockIContentProvider).call(any(), any(),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
+        verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
                 any(), any(Bundle.class));
 
+        Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
+                NAMESPACE, Collections.emptyList());
+        verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_LIST_CONFIG),
+                any(), any(Bundle.class));
+        assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
+
         HashMap<String, String> keyValues2 = new HashMap<>();
         keyValues2.put("c", "d");
         keyValues2.put("e", "f");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE2, keyValues2);
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
-                any(), any(Bundle.class));
-
-        Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE,
-                Collections.emptyList());
-        verify(mMockIContentProvider).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class));
-        assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
 
         Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE2,
-                Collections.emptyList());
+                NAMESPACE2, Collections.emptyList());
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class));
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
         assertThat(returnedValues2).containsExactlyEntriesIn(keyValues2);
 
         Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
                 NAMESPACE, Collections.emptyList());
+        // Modifying the second namespace doesn't affect the cache of the first namespace
         verifyNoMoreInteractions(mMockIContentProvider);
         assertThat(cachedKeyValues).containsExactlyEntriesIn(keyValues);
 
@@ -219,17 +287,90 @@
     @Test
     public void testCaching_emptyNamespace() throws Exception {
         Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE,
-                Collections.emptyList());
-        verify(mMockIContentProvider).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class));
+                NAMESPACE, Collections.emptyList());
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
         assertThat(returnedValues).isEmpty();
 
         Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
                 NAMESPACE, Collections.emptyList());
-        verifyNoMoreInteractions(mMockIContentProvider);
+        // Empty list won't be cached
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
         assertThat(cachedKeyValues).isEmpty();
     }
 
+    @Test
+    public void testCaching_singleSetting() throws Exception {
+        Settings.Secure.putString(mMockContentResolver, SETTING, "a");
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+        String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue).isEqualTo("a");
+
+        String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verifyNoMoreInteractions(mMockIContentProvider);
+        assertThat(cachedValue).isEqualTo("a");
+
+        // Modify the value to invalidate the cache.
+        Settings.Secure.putString(mMockContentResolver, SETTING, "b");
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+        String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue2).isEqualTo("b");
+
+        String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verifyNoMoreInteractions(mMockIContentProvider);
+        assertThat(cachedValue2).isEqualTo("b");
+    }
+
+    @Test
+    public void testCaching_multipleSettings() throws Exception {
+        Settings.Secure.putString(mMockContentResolver, SETTING, "a");
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+        String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue).isEqualTo("a");
+
+        Settings.Secure.putString(mMockContentResolver, SETTING2, "b");
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+        String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue2).isEqualTo("b");
+
+        String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        // Modifying the second setting doesn't affect the cache of the first setting
+        verifyNoMoreInteractions(mMockIContentProvider);
+        assertThat(cachedValue).isEqualTo("a");
+
+        String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
+        verifyNoMoreInteractions(mMockIContentProvider);
+        assertThat(cachedValue2).isEqualTo("b");
+    }
+
+    @Test
+    public void testCaching_nullSetting() throws Exception {
+        String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue).isNull();
+
+        String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        // Empty list won't be cached
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(cachedValue).isNull();
+    }
 }
diff --git a/core/tests/coretests/src/android/view/KeyEventTest.java b/core/tests/coretests/src/android/view/KeyEventTest.java
index 32078ca..f723f15 100644
--- a/core/tests/coretests/src/android/view/KeyEventTest.java
+++ b/core/tests/coretests/src/android/view/KeyEventTest.java
@@ -47,14 +47,14 @@
     private static final int ACTION = KeyEvent.ACTION_DOWN;
     private static final int ANOTHER_ACTION = KeyEvent.ACTION_UP;
     private static final int KEYCODE = KeyEvent.KEYCODE_0;
-    private static final int REPEAT = 0;
-    private static final int ANOTHER_REPEAT = 42;
-    private static final int METASTATE = 0;
-    private static final int DEVICE_ID = 0;
-    private static final int SCAN_CODE = 0;
-    private static final int FLAGS = 0;
+    private static final int REPEAT = 4;
+    private static final int ANOTHER_REPEAT = 8;
+    private static final int METASTATE = 15;
+    private static final int DEVICE_ID = 16;
+    private static final int SCAN_CODE = 23;
+    private static final int FLAGS = 42;
     private static final int SOURCE = InputDevice.SOURCE_KEYBOARD;
-    private static final String CHARACTERS = null;
+    private static final String CHARACTERS = "CHARACTERS, Y U NO @NONNULL?";
 
     private static final int ID_SOURCE_MASK = 0x3 << 30;
 
@@ -178,7 +178,7 @@
                 DEVICE_ID, SCAN_CODE, FLAGS, SOURCE);
 
         assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
-                DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, INVALID_DISPLAY, CHARACTERS);
+                DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, INVALID_DISPLAY, /* characters= */ null);
     }
 
     @Test
@@ -187,7 +187,8 @@
                 DEVICE_ID, SCAN_CODE, FLAGS);
 
         assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
-                DEVICE_ID, SCAN_CODE, FLAGS, /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+                DEVICE_ID, SCAN_CODE, FLAGS, /* source= */ 0, INVALID_DISPLAY,
+                /* characters= */ null);
     }
 
     @Test
@@ -197,7 +198,7 @@
 
         assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
                 DEVICE_ID, SCAN_CODE, /* flags= */ 0, /* source= */ 0, INVALID_DISPLAY,
-                CHARACTERS);
+                /* characters= */ null);
     }
 
     @Test
@@ -206,7 +207,7 @@
 
         assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
                 /* deviceId= */ KeyCharacterMap.VIRTUAL_KEYBOARD, /* scanCode= */ 0, /* flags= */ 0,
-                /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+                /* source= */ 0, INVALID_DISPLAY, /* characters= */ null);
     }
 
     @Test
@@ -215,7 +216,8 @@
 
         assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
                 /* metaState= */ 0, /* deviceId= */ KeyCharacterMap.VIRTUAL_KEYBOARD,
-                /* scanCode= */ 0, /* flags= */ 0, /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+                /* scanCode= */ 0, /* flags= */ 0, /* source= */ 0, INVALID_DISPLAY,
+                /* characters= */ null);
     }
 
     @Test
@@ -233,8 +235,8 @@
 
         assertKeyEventFields(key, /* downTime= */ 0, /* eventTime= */ 0, ACTION, KEYCODE,
                 /* repeat= */ 0, /* metaState= */ 0,
-                /* deviceId= */ KeyCharacterMap.VIRTUAL_KEYBOARD, /* scanCode= */ 0, FLAGS,
-                /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+                /* deviceId= */ KeyCharacterMap.VIRTUAL_KEYBOARD, /* scanCode= */ 0, /* flags= */ 0,
+                /* source= */ 0, INVALID_DISPLAY, /* characters= */ null);
     }
 
     @Test
@@ -244,7 +246,7 @@
 
         assertKeyEventFields(key2, DOWN_TIME, EVENT_TIME, ANOTHER_ACTION, KEYCODE, REPEAT,
                 METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, InputDevice.SOURCE_KEYBOARD,
-                INVALID_DISPLAY, CHARACTERS);
+                INVALID_DISPLAY, /* characters= */ null);
         expect.withMessage("id (key1=%s, key2=%s", key1, key2).that(key2.getId())
                 .isNotEqualTo(key1.getId());
     }
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 95aa5d0..76f5277 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -32,7 +32,6 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
-import android.view.HandwritingDelegateConfiguration;
 import android.view.HandwritingInitiator;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -210,14 +209,11 @@
 
     @Test
     public void onTouchEvent_startHandwriting_delegate() {
-        int delegatorViewId = 234;
-        View delegatorView = new View(mContext);
-        delegatorView.setId(delegatorViewId);
+        View delegateView = new View(mContext);
+        delegateView.setIsHandwritingDelegate(true);
 
-        mTestView.setHandwritingDelegateConfiguration(
-                new HandwritingDelegateConfiguration(
-                        delegatorViewId,
-                        () -> mHandwritingInitiator.onInputConnectionCreated(delegatorView)));
+        mTestView.setHandwritingDelegatorCallback(
+                () -> mHandwritingInitiator.onInputConnectionCreated(delegateView));
 
         final int x1 = (sHwArea.left + sHwArea.right) / 2;
         final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
@@ -229,7 +225,7 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        verify(mHandwritingInitiator, times(1)).startHandwriting(delegatorView);
+        verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(delegateView);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
index 0c7550e..777246b 100644
--- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
@@ -33,6 +33,8 @@
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
 import android.view.ContextMenu;
 import android.view.MenuItem;
@@ -167,4 +169,75 @@
         assertThat(idCaptor.getValue()).isEqualTo(TextView.ID_ASSIST);
         assertThat(titleCaptor.getValue().toString()).isEqualTo(ACTION_TITLE);
     }
+
+    @UiThreadTest
+    @Test
+    public void testAdjustIconSpaces() {
+        GradientDrawable gd = new GradientDrawable();
+        gd.setSize(128, 256);
+
+        // Setup mocks
+        ContextMenu menu = mock(ContextMenu.class);
+
+        MenuItem mockIconMenu = newMockMenuItem();
+        when(mockIconMenu.getIcon()).thenReturn(gd);
+
+        MenuItem mockNoIconMenu = newMockMenuItem();
+        when(mockNoIconMenu.getIcon()).thenReturn(null);
+
+        MenuItem mockNoIconMenu2 = newMockMenuItem();
+        when(mockNoIconMenu2.getIcon()).thenReturn(null);
+
+        when(menu.size()).thenReturn(3);
+        when(menu.getItem(0)).thenReturn(mockIconMenu);
+        when(menu.getItem(1)).thenReturn(mockNoIconMenu);
+        when(menu.getItem(2)).thenReturn(mockNoIconMenu2);
+
+
+        // Execute the test method
+        EditText et = mActivity.findViewById(R.id.editText);
+        Editor editor = et.getEditorForTesting();
+        editor.adjustIconSpacing(menu);
+
+        // Verify
+        ArgumentCaptor<Drawable> drawableCaptor = ArgumentCaptor.forClass(Drawable.class);
+        verify(mockNoIconMenu).setIcon(drawableCaptor.capture());
+
+        Drawable paddingDrawable = drawableCaptor.getValue();
+        assertThat(paddingDrawable).isNotNull();
+        assertThat(paddingDrawable.getIntrinsicWidth()).isEqualTo(128);
+        assertThat(paddingDrawable.getIntrinsicHeight()).isEqualTo(256);
+
+        ArgumentCaptor<Drawable> drawableCaptor2 = ArgumentCaptor.forClass(Drawable.class);
+        verify(mockNoIconMenu2).setIcon(drawableCaptor2.capture());
+
+        Drawable paddingDrawable2 = drawableCaptor2.getValue();
+        assertThat(paddingDrawable2).isSameInstanceAs(paddingDrawable);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAdjustIconSpacesNoIconCase() {
+        // Setup mocks
+        ContextMenu menu = mock(ContextMenu.class);
+
+        MenuItem mockNoIconMenu = newMockMenuItem();
+        when(mockNoIconMenu.getIcon()).thenReturn(null);
+
+        MenuItem mockNoIconMenu2 = newMockMenuItem();
+        when(mockNoIconMenu2.getIcon()).thenReturn(null);
+
+        when(menu.size()).thenReturn(2);
+        when(menu.getItem(0)).thenReturn(mockNoIconMenu);
+        when(menu.getItem(1)).thenReturn(mockNoIconMenu2);
+
+        // Execute the test method
+        EditText et = mActivity.findViewById(R.id.editText);
+        Editor editor = et.getEditorForTesting();
+        editor.adjustIconSpacing(menu);
+
+        // Verify
+        verify(mockNoIconMenu, times(0)).setIcon(any());
+        verify(mockNoIconMenu2, times(0)).setIcon(any());
+    }
 }
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index 9ccf3b3..3b8b8c7 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -117,6 +117,31 @@
     }
 
     @Test
+    public void testGetReportedHdrTypes_returns_mode_specific_hdr_types() {
+        setDisplayInfoPortrait(mDisplayInfo);
+        float[] alternativeRefreshRates = new float[0];
+        int[] hdrTypesWithDv = new int[] {1, 2, 3, 4};
+        Display.Mode modeWithDv = new Display.Mode(/* modeId= */ 0, 0, 0, 0f,
+                alternativeRefreshRates, hdrTypesWithDv);
+
+        int[] hdrTypesWithoutDv = new int[]{2, 3, 4};
+        Display.Mode modeWithoutDv = new Display.Mode(/* modeId= */ 1, 0, 0, 0f,
+                alternativeRefreshRates, hdrTypesWithoutDv);
+
+        mDisplayInfo.supportedModes = new Display.Mode[] {modeWithoutDv, modeWithDv};
+        mDisplayInfo.hdrCapabilities = new Display.HdrCapabilities(hdrTypesWithDv, 0, 0, 0);
+
+        final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+
+        mDisplayInfo.modeId = 0;
+        assertArrayEquals(hdrTypesWithDv, display.getReportedHdrTypes());
+
+        mDisplayInfo.modeId = 1;
+        assertArrayEquals(hdrTypesWithoutDv, display.getReportedHdrTypes());
+    }
+
+    @Test
     public void testConstructor_defaultDisplayAdjustments_matchesDisplayInfo() {
         setDisplayInfoPortrait(mDisplayInfo);
         final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 71050fa..f3318f4 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -122,6 +122,7 @@
         <permission name="android.permission.BIND_CARRIER_SERVICES"/>
         <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
         <permission name="android.permission.BIND_IMS_SERVICE"/>
+        <permission name="android.permission.BIND_SATELLITE_SERVICE"/>
         <permission name="android.permission.BIND_TELEPHONY_DATA_SERVICE"/>
         <permission name="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"/>
         <permission name="android.permission.CALL_PRIVILEGED"/>
@@ -508,6 +509,8 @@
         <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
         <!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
         <permission name="android.permission.WRITE_APN_SETTINGS"/>
+        <!-- Permission required for GTS test - GtsStatsdHostTestCases -->
+        <permission name="android.permission.READ_RESTRICTED_STATS"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
index e2012b4..88525aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell;
 
-import android.os.Build;
-
 import com.android.wm.shell.protolog.ShellProtoLogImpl;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellInit;
@@ -43,9 +41,6 @@
 
     void onInit() {
         mShellCommandHandler.addCommandCallback("protolog", this, this);
-        if (Build.IS_DEBUGGABLE) {
-            mShellProtoLog.startProtoLog(null /* PrintWriter */);
-        }
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index acf17e6..b3fff1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -29,8 +29,9 @@
 import android.database.ContentObserver;
 import android.hardware.input.InputManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -39,7 +40,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.IRemoteAnimationRunner;
-import android.view.IWindowFocusObserver;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -121,23 +121,22 @@
     private IOnBackInvokedCallback mActiveCallback;
 
     @VisibleForTesting
-    final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
-        @Override
-        public void focusGained(IBinder inputToken) { }
-        @Override
-        public void focusLost(IBinder inputToken) {
-            mShellExecutor.execute(() -> {
-                if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
-                    // If an uninterruptible animation is already in progress, we should ignore
-                    // this due to it may cause focus lost. (alpha = 0)
-                    return;
+    final RemoteCallback mNavigationObserver = new RemoteCallback(
+            new RemoteCallback.OnResultListener() {
+                @Override
+                public void onResult(@Nullable Bundle result) {
+                    mShellExecutor.execute(() -> {
+                        if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
+                            // If an uninterruptible animation is already in progress, we should
+                            // ignore this due to it may cause focus lost. (alpha = 0)
+                            return;
+                        }
+                        ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone.");
+                        setTriggerBack(false);
+                        onGestureFinished(false);
+                    });
                 }
-                ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Target window lost focus.");
-                setTriggerBack(false);
-                onGestureFinished(false);
             });
-        }
-    };
 
     private final BackAnimationBackground mAnimationBackground;
 
@@ -351,7 +350,7 @@
 
         try {
             mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
-                    mFocusObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
+                    mNavigationObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
             onBackNavigationInfoReceived(mBackNavigationInfo);
         } catch (RemoteException remoteException) {
             Log.e(TAG, "Failed to initAnimation", remoteException);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 5933ac2..713dd39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -59,9 +59,6 @@
     public static final long TOUCH_ANIMATION_DURATION = 150;
     public static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
 
-    /** The task bar expanded height. Used to determine whether to insets divider bounds or not. */
-    private float mExpandedTaskBarHeight;
-
     private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 
     private SplitLayout mSplitLayout;
@@ -216,17 +213,19 @@
 
     void onInsetsChanged(InsetsState insetsState, boolean animate) {
         mSplitLayout.getDividerBounds(mTempRect);
-        final InsetsSource taskBarInsetsSource =
-                insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
         // Only insets the divider bar with task bar when it's expanded so that the rounded corners
         // will be drawn against task bar.
         // But there is no need to do it when IME showing because there are no rounded corners at
         // the bottom. This also avoids the problem of task bar height not changing when IME
         // floating.
-        if (!insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())
-                && taskBarInsetsSource != null
-                && taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
-            mTempRect.inset(taskBarInsetsSource.calculateVisibleInsets(mTempRect));
+        if (!insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())) {
+            for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
+                final InsetsSource source = insetsState.sourceAt(i);
+                if (source.getType() == WindowInsets.Type.navigationBars()
+                        && source.insetsRoundedCornerFrame()) {
+                    mTempRect.inset(source.calculateVisibleInsets(mTempRect));
+                }
+            }
         }
 
         if (!mTempRect.equals(mDividerBounds)) {
@@ -251,8 +250,6 @@
         mDividerBar = findViewById(R.id.divider_bar);
         mHandle = findViewById(R.id.docked_divider_handle);
         mBackground = findViewById(R.id.docked_divider_background);
-        mExpandedTaskBarHeight = getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.taskbar_frame_height);
         mTouchElevation = getResources().getDimensionPixelSize(
                 R.dimen.docked_stack_divider_lift_elevation);
         mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f616e6f..8a18271 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -1091,7 +1091,7 @@
             // ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough
             // because DividerView won't receive onImeVisibilityChanged callback after it being
             // re-inflated.
-            mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus,
+            mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus || isFloating,
                     "onImeStartPositioning");
 
             return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index fce0138..73a7403 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -85,7 +85,7 @@
     fun showDesktopApps() {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps")
         val wct = WindowContainerTransaction()
-        bringDesktopAppsToFront(wct, force = true)
+        bringDesktopAppsToFront(wct)
 
         // Execute transaction if there are pending operations
         if (!wct.isEmpty) {
@@ -156,19 +156,9 @@
             ?: WINDOWING_MODE_UNDEFINED
     }
 
-    private fun bringDesktopAppsToFront(wct: WindowContainerTransaction, force: Boolean = false) {
-        val activeTasks = desktopModeTaskRepository.getActiveTasks()
-
-        // Skip if all tasks are already visible
-        if (!force && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) {
-            ProtoLog.d(
-                WM_SHELL_DESKTOP_MODE,
-                "bringDesktopAppsToFront: active tasks are already in front, skipping."
-            )
-            return
-        }
-
+    private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront")
+        val activeTasks = desktopModeTaskRepository.getActiveTasks()
 
         // First move home to front and then other tasks on top of it
         moveHomeTaskToFront(wct)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 26f47fc..d094c22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -108,6 +108,10 @@
         }
         if (!createdWindowDecor) {
             mSyncQueue.runInSync(t -> {
+                if (!leash.isValid()) {
+                    // Task vanished before sync completion
+                    return;
+                }
                 // Reset several properties back to fullscreen (PiP, for example, leaves all these
                 // properties in a bad state).
                 t.setWindowCrop(leash, null);
@@ -136,6 +140,10 @@
         final Point positionInParent = state.mTaskInfo.positionInParent;
         if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
             mSyncQueue.runInSync(t -> {
+                if (!state.mLeash.isValid()) {
+                    // Task vanished before sync completion
+                    return;
+                }
                 t.setPosition(state.mLeash, positionInParent.x, positionInParent.y);
             });
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index ac13f96..9796e4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -36,6 +36,7 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
+import android.view.WindowInsets;
 import android.window.ITaskOrganizerController;
 import android.window.TaskAppearedInfo;
 import android.window.WindowContainerToken;
@@ -57,7 +58,6 @@
 
 import java.io.PrintWriter;
 import java.util.List;
-import java.util.Objects;
 import java.util.Optional;
 
 /**
@@ -130,14 +130,34 @@
             new DisplayInsetsController.OnInsetsChangedListener() {
         @Override
         public void insetsChanged(InsetsState insetsState) {
-            // Update bounds only when the insets of navigation bar or task bar is changed.
-            if (Objects.equals(insetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR),
-                    mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR))
-                    && Objects.equals(insetsState.peekSource(
-                            InsetsState.ITYPE_EXTRA_NAVIGATION_BAR),
-                    mInsetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR))) {
+            final boolean[] navigationBarChanged = {false};
+            InsetsState.traverse(insetsState, mInsetsState, new InsetsState.OnTraverseCallbacks() {
+                @Override
+                public void onIdMatch(InsetsSource source1, InsetsSource source2) {
+                    if (source1.getType() == WindowInsets.Type.navigationBars()
+                            && !source1.equals(source2)) {
+                        navigationBarChanged[0] = true;
+                    }
+                }
+
+                @Override
+                public void onIdNotFoundInState1(int index2, InsetsSource source2) {
+                    if (source2.getType() == WindowInsets.Type.navigationBars()) {
+                        navigationBarChanged[0] = true;
+                    }
+                }
+
+                @Override
+                public void onIdNotFoundInState2(int index1, InsetsSource source1) {
+                    if (source1.getType() == WindowInsets.Type.navigationBars()) {
+                        navigationBarChanged[0] = true;
+                    }
+                }
+            });
+            if (!navigationBarChanged[0]) {
                 return;
             }
+            // Update bounds only when the insets of navigation bar or task bar is changed.
             mInsetsState.set(insetsState);
             updateBounds();
         }
@@ -344,16 +364,8 @@
 
     private Rect calculateBounds() {
         final Rect bounds = new Rect(0, 0, mDisplayWidth, mDisplayHeight);
-        final InsetsSource navBarSource = mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR);
-        final InsetsSource taskBarSource = mInsetsState.peekSource(
-                InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
-        if (navBarSource != null && !navBarSource.getFrame().isEmpty()) {
-            bounds.inset(navBarSource.calculateInsets(bounds, false /* ignoreVisibility */));
-        } else if (taskBarSource != null && !taskBarSource.getFrame().isEmpty()) {
-            bounds.inset(taskBarSource.calculateInsets(bounds, false /* ignoreVisibility */));
-        } else {
-            bounds.setEmpty();
-        }
+        bounds.inset(mInsetsState.calculateInsets(
+                bounds, WindowInsets.Type.navigationBars(), false /* ignoreVisibility */));
         return bounds;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index c59c42d..93ffb3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -31,7 +31,8 @@
  */
 public class ShellProtoLogImpl extends BaseProtoLogImpl {
     private static final String TAG = "ProtoLogImpl";
-    private static final int BUFFER_CAPACITY = 128 * 1024;
+    private static final int BUFFER_CAPACITY = 1024 * 1024;
+    // TODO: find a proper location to save the protolog message file
     private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
     private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index 06a8438..f81fc6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -35,6 +35,7 @@
 import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets;
 
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.wm.shell.common.DisplayInsetsController;
@@ -66,13 +67,12 @@
     private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2;
 
     private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
-    private final int mExpandedTaskBarHeight;
     private final DisplayInsetsController mDisplayInsetsController;
     private final UnfoldBackgroundController mBackgroundController;
     private final Context mContext;
     private final ShellController mShellController;
 
-    private InsetsSource mTaskbarInsetsSource;
+    private InsetsSource mExpandedTaskbarInsetsSource;
     private float mWindowCornerRadiusPx;
 
     public FullscreenUnfoldTaskAnimator(Context context,
@@ -82,8 +82,6 @@
         mDisplayInsetsController = displayInsetsController;
         mBackgroundController = backgroundController;
         mShellController = shellController;
-        mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.taskbar_frame_height);
         mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
     }
 
@@ -101,21 +99,32 @@
 
     @Override
     public void insetsChanged(InsetsState insetsState) {
-        mTaskbarInsetsSource = insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+        mExpandedTaskbarInsetsSource = getExpandedTaskbarSource(insetsState);
         for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
             AnimationContext context = mAnimationContextByTaskId.valueAt(i);
-            context.update(mTaskbarInsetsSource, context.mTaskInfo);
+            context.update(mExpandedTaskbarInsetsSource, context.mTaskInfo);
         }
     }
 
+    private static InsetsSource getExpandedTaskbarSource(InsetsState state) {
+        for (int i = state.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = state.sourceAt(i);
+            if (source.getType() == WindowInsets.Type.navigationBars()
+                    && source.insetsRoundedCornerFrame()) {
+                return source;
+            }
+        }
+        return null;
+    }
+
     public boolean hasActiveTasks() {
         return mAnimationContextByTaskId.size() > 0;
     }
 
     @Override
     public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {
-        AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
-                taskInfo);
+        AnimationContext animationContext = new AnimationContext(
+                leash, mExpandedTaskbarInsetsSource, taskInfo);
         mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
     }
 
@@ -123,7 +132,7 @@
     public void onTaskChanged(TaskInfo taskInfo) {
         AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
         if (animationContext != null) {
-            animationContext.update(mTaskbarInsetsSource, taskInfo);
+            animationContext.update(mExpandedTaskbarInsetsSource, taskInfo);
         }
     }
 
@@ -222,11 +231,7 @@
             mStartCropRect.set(mTaskInfo.getConfiguration().windowConfiguration.getBounds());
 
             if (taskBarInsetsSource != null) {
-                // Only insets the cropping window with task bar when it's expanded
-                if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
-                    mStartCropRect.inset(taskBarInsetsSource
-                            .calculateVisibleInsets(mStartCropRect));
-                }
+                mStartCropRect.inset(taskBarInsetsSource.calculateVisibleInsets(mStartCropRect));
             }
 
             mEndCropRect.set(mStartCropRect);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index f835c7b..2f0c964 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -37,6 +37,7 @@
 import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets;
 
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.wm.shell.common.DisplayInsetsController;
@@ -76,7 +77,6 @@
     private final Executor mExecutor;
     private final DisplayInsetsController mDisplayInsetsController;
     private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
-    private final int mExpandedTaskBarHeight;
     private final ShellController mShellController;
     private final Lazy<Optional<SplitScreenController>> mSplitScreenController;
     private final UnfoldBackgroundController mUnfoldBackgroundController;
@@ -86,7 +86,7 @@
     private final Rect mRootStageBounds = new Rect();
 
     private float mWindowCornerRadiusPx;
-    private InsetsSource mTaskbarInsetsSource;
+    private InsetsSource mExpandedTaskbarInsetsSource;
 
     @SplitPosition
     private int mMainStagePosition = SPLIT_POSITION_UNDEFINED;
@@ -103,8 +103,6 @@
         mShellController = shellController;
         mUnfoldBackgroundController = unfoldBackgroundController;
         mSplitScreenController = splitScreenController;
-        mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.taskbar_frame_height);
         mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
     }
 
@@ -143,10 +141,21 @@
 
     @Override
     public void insetsChanged(InsetsState insetsState) {
-        mTaskbarInsetsSource = insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+        mExpandedTaskbarInsetsSource = getExpandedTaskbarSource(insetsState);
         updateContexts();
     }
 
+    private static InsetsSource getExpandedTaskbarSource(InsetsState state) {
+        for (int i = state.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = state.sourceAt(i);
+            if (source.getType() == WindowInsets.Type.navigationBars()
+                    && source.insetsRoundedCornerFrame()) {
+                return source;
+            }
+        }
+        return null;
+    }
+
     @Override
     public void onTaskStageChanged(int taskId, int stage, boolean visible) {
         final AnimationContext context = mAnimationContextByTaskId.get(taskId);
@@ -307,7 +316,8 @@
             boolean taskbarExpanded = isTaskbarExpanded();
             if (taskbarExpanded) {
                 // Only insets the cropping window with taskbar when taskbar is expanded
-                mStartCropRect.inset(mTaskbarInsetsSource.calculateVisibleInsets(mStartCropRect));
+                mStartCropRect.inset(mExpandedTaskbarInsetsSource.calculateVisibleInsets(
+                        mStartCropRect));
             }
 
             // Offset to surface coordinates as layout bounds are in screen coordinates
@@ -360,8 +370,7 @@
         }
 
         private boolean isTaskbarExpanded() {
-            return mTaskbarInsetsSource != null
-                    && mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight;
+            return mExpandedTaskbarInsetsSource != null;
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 2981f5e..9224b3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -174,7 +174,7 @@
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
         final TaskPositioner taskPositioner =
-                new TaskPositioner(mTaskOrganizer, windowDecoration);
+                new TaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController);
         final CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 75bc985..766c4cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -558,7 +558,8 @@
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
         final TaskPositioner taskPositioner =
-                new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
+                new TaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
+                        mDragStartListener);
         final DesktopModeTouchEventListener touchEventListener =
                 new DesktopModeTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index d3f9227..a3d364a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -19,9 +19,11 @@
 import android.annotation.IntDef;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.util.DisplayMetrics;
 import android.window.WindowContainerTransaction;
 
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
 
 class TaskPositioner implements DragPositioningCallback {
 
@@ -35,6 +37,7 @@
     static final int CTRL_TYPE_BOTTOM = 8;
 
     private final ShellTaskOrganizer mTaskOrganizer;
+    private final DisplayController mDisplayController;
     private final WindowDecoration mWindowDecoration;
 
     private final Rect mTaskBoundsAtDragStart = new Rect();
@@ -45,14 +48,16 @@
     private int mCtrlType;
     private DragStartListener mDragStartListener;
 
-    TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
-        this(taskOrganizer, windowDecoration, dragStartListener -> {});
+    TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+            DisplayController displayController) {
+        this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {});
     }
 
     TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DragStartListener dragStartListener) {
+            DisplayController displayController, DragStartListener dragStartListener) {
         mTaskOrganizer = taskOrganizer;
         mWindowDecoration = windowDecoration;
+        mDisplayController = displayController;
         mDragStartListener = dragStartListener;
     }
 
@@ -128,15 +133,44 @@
             mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
         }
 
+        // If width or height are negative or less than the minimum width or height, revert the
+        // respective bounds to use previous bound dimensions.
+        if (mRepositionTaskBounds.width() < getMinWidth()) {
+            mRepositionTaskBounds.right = oldRight;
+            mRepositionTaskBounds.left = oldLeft;
+        }
+        if (mRepositionTaskBounds.height() < getMinHeight()) {
+            mRepositionTaskBounds.top = oldTop;
+            mRepositionTaskBounds.bottom = oldBottom;
+        }
+        // If there are no changes to the bounds after checking new bounds against minimum width
+        // and height, do not set bounds and return false
         if (oldLeft == mRepositionTaskBounds.left && oldTop == mRepositionTaskBounds.top
                 && oldRight == mRepositionTaskBounds.right
                 && oldBottom == mRepositionTaskBounds.bottom) {
             return false;
         }
+
         wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
         return true;
     }
 
+    private float getMinWidth() {
+        return mWindowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinSize()
+                : mWindowDecoration.mTaskInfo.minWidth;
+    }
+
+    private float getMinHeight() {
+        return mWindowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinSize()
+                : mWindowDecoration.mTaskInfo.minHeight;
+    }
+
+    private float getDefaultMinSize() {
+        float density =  mDisplayController.getDisplayLayout(mWindowDecoration.mTaskInfo.displayId)
+                .densityDpi() * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+        return mWindowDecoration.mTaskInfo.defaultMinSize * density;
+    }
+
     interface DragStartListener {
         /**
          * Inform the implementing class that a drag resize has started
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 8a5b490..5a4a44f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -25,7 +25,6 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
@@ -40,7 +39,6 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.provider.Settings;
@@ -341,8 +339,7 @@
         mController.setTriggerBack(true);   // Fake trigger back
 
         // In case the focus has been changed.
-        IBinder token = mock(IBinder.class);
-        mController.mFocusObserver.focusLost(token);
+        mController.mNavigationObserver.sendResult(null);
         mShellExecutor.flushAll();
         verify(mAnimatorCallback).onBackCancelled();
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
index f185a8a..8f66f4e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -8,6 +8,8 @@
 import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
 import androidx.test.filters.SmallTest
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
@@ -45,6 +47,11 @@
     @Mock
     private lateinit var taskBinder: IBinder
 
+    @Mock
+    private lateinit var mockDisplayController: DisplayController
+    @Mock
+    private lateinit var mockDisplayLayout: DisplayLayout
+
     private lateinit var taskPositioner: TaskPositioner
 
     @Before
@@ -54,12 +61,21 @@
         taskPositioner = TaskPositioner(
                 mockShellTaskOrganizer,
                 mockWindowDecoration,
+                mockDisplayController,
                 mockDragStartListener
         )
+
         `when`(taskToken.asBinder()).thenReturn(taskBinder)
+        `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+        `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+
         mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
             taskId = TASK_ID
             token = taskToken
+            minWidth = MIN_WIDTH
+            minHeight = MIN_HEIGHT
+            defaultMinSize = DEFAULT_MIN
+            displayId = DISPLAY_ID
             configuration.windowConfiguration.bounds = STARTING_BOUNDS
         }
     }
@@ -209,8 +225,239 @@
         })
     }
 
+    @Test
+    fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenLessThanMin() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+                STARTING_BOUNDS.right.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Resize to width of 95px and height of 5px with min width of 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 5
+        val newY = STARTING_BOUNDS.top.toFloat() + 95
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragPositioningEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+                                != 0) && change.configuration.windowConfiguration.bounds.top ==
+                        STARTING_BOUNDS.top &&
+                        change.configuration.windowConfiguration.bounds.bottom ==
+                        STARTING_BOUNDS.bottom
+            }
+        })
+    }
+
+    @Test
+    fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenLessThanMin() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+                STARTING_BOUNDS.right.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Resize to height of 95px and width of 5px with min width of 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 95
+        val newY = STARTING_BOUNDS.top.toFloat() + 5
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragPositioningEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+                                != 0) && change.configuration.windowConfiguration.bounds.right ==
+                        STARTING_BOUNDS.right &&
+                        change.configuration.windowConfiguration.bounds.left ==
+                        STARTING_BOUNDS.left
+            }
+        })
+    }
+
+    @Test
+    fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenNegative() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+                STARTING_BOUNDS.right.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Resize to height of -5px and width of 95px
+        val newX = STARTING_BOUNDS.right.toFloat() - 5
+        val newY = STARTING_BOUNDS.top.toFloat() + 105
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragPositioningEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+                                != 0) && change.configuration.windowConfiguration.bounds.top ==
+                        STARTING_BOUNDS.top &&
+                        change.configuration.windowConfiguration.bounds.bottom ==
+                        STARTING_BOUNDS.bottom
+            }
+        })
+    }
+
+    @Test
+    fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenNegative() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+                STARTING_BOUNDS.right.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Resize to width of -5px and height of 95px
+        val newX = STARTING_BOUNDS.right.toFloat() - 105
+        val newY = STARTING_BOUNDS.top.toFloat() + 5
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragPositioningEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+                                != 0) && change.configuration.windowConfiguration.bounds.right ==
+                        STARTING_BOUNDS.right &&
+                        change.configuration.windowConfiguration.bounds.left ==
+                        STARTING_BOUNDS.left
+            }
+        })
+    }
+
+    @Test
+    fun testDragResize_resize_setBoundsRunsWhenResizeBoundsValid() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+                STARTING_BOUNDS.right.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Shrink to height 20px and width 20px with both min height/width equal to 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 80
+        val newY = STARTING_BOUNDS.top.toFloat() + 80
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragPositioningEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+            }
+        })
+    }
+
+    @Test
+    fun testDragResize_resize_setBoundsDoesNotRunWithNegativeHeightAndWidth() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+                STARTING_BOUNDS.right.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Shrink to height 5px and width 5px with both min height/width equal to 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 95
+        val newY = STARTING_BOUNDS.top.toFloat() + 95
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragPositioningEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+            }
+        })
+    }
+
+    @Test
+    fun testDragResize_resize_useDefaultMinWhenMinWidthInvalid() {
+        mockWindowDecoration.mTaskInfo.minWidth = -1
+
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+                STARTING_BOUNDS.right.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Shrink to width and height of 3px with invalid minWidth = -1 and defaultMinSize = 5px
+        val newX = STARTING_BOUNDS.right.toFloat() - 97
+        val newY = STARTING_BOUNDS.top.toFloat() + 97
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragPositioningEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+            }
+        })
+    }
+
+    @Test
+    fun testDragResize_resize_useMinWidthWhenValid() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+                STARTING_BOUNDS.right.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // Shrink to width and height of 7px with valid minWidth = 10px and defaultMinSize = 5px
+        val newX = STARTING_BOUNDS.right.toFloat() - 93
+        val newY = STARTING_BOUNDS.top.toFloat() + 93
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+
+        taskPositioner.onDragPositioningEnd(newX, newY)
+
+        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+            }
+        })
+    }
+
     companion object {
         private const val TASK_ID = 5
+        private const val MIN_WIDTH = 10
+        private const val MIN_HEIGHT = 10
+        private const val DENSITY_DPI = 20
+        private const val DEFAULT_MIN = 40
+        private const val DISPLAY_ID = 1
         private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
     }
 }
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 0b08681..faa7f7f 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -3049,7 +3049,13 @@
 
 
         /**
-         * Gets the {@link LogSessionId}.
+         * Sets the {@link LogSessionId}.
+         *
+         * <p>The implementation of this method varies by DRM provider; Please refer
+         * to your DRM provider documentation for more details on this method.
+         *
+         * @throws UnsupportedOperationException when the vendor plugin does not
+         * implement this method
          */
         public void setLogSessionId(@NonNull LogSessionId logSessionId) {
             Objects.requireNonNull(logSessionId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index afc2bb1..6eed483 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -190,9 +190,7 @@
                 break;
             }
             case DO_SEND_TIME_SHIFT_MODE: {
-                SomeArgs args = (SomeArgs) msg.obj;
-                mSessionImpl.sendTimeShiftMode(args.argi1);
-                args.recycle();
+                mSessionImpl.sendTimeShiftMode((Integer) msg.obj);
                 break;
             }
             case DO_SEND_AVAILABLE_SPEEDS: {
@@ -447,7 +445,7 @@
 
     @Override
     public void sendTimeShiftMode(int mode) {
-        mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_SEND_TIME_SHIFT_MODE, mode));
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SEND_TIME_SHIFT_MODE, mode));
     }
 
     @Override
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index b36cb5c..dfc8aa0 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -41,6 +41,15 @@
         android:excludeFromRecents="true"
         android:theme="@style/Theme.CredentialSelector">
     </activity>
+
+    <receiver
+        android:name=".CredentialProviderReceiver"
+        android:exported="true"
+        android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR">
+        <intent-filter>
+            <action android:name="android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED"/>
+        </intent-filter>
+    </receiver>
   </application>
 
 </manifest>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 0d25bec..30b97bf 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -73,7 +73,7 @@
         requestInfo = intent.extras?.getParcelable(
             RequestInfo.EXTRA_REQUEST_INFO,
             RequestInfo::class.java
-        ) ?: testCreatePasskeyRequestInfo()
+        ) ?: testCreatePasswordRequestInfo()
 
         providerEnabledList = when (requestInfo.type) {
             RequestInfo.TYPE_CREATE ->
@@ -402,30 +402,26 @@
         )
         val credentialData = request.credentialData
         return RequestInfo.newCreateRequestInfo(
-            Binder(),
-            CreateCredentialRequest(
-                "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
-                credentialData,
-                /*candidateQueryData=*/ Bundle(),
-                /*isSystemProviderRequired=*/ false,
-                /*alwaysSendAppInfoToProvider=*/ true
-            ),
-            "com.google.android.youtube"
+                Binder(),
+                CreateCredentialRequest.Builder(credentialData, Bundle())
+                        .setType("androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL")
+                        .setIsSystemProviderRequired(false)
+                        .setAlwaysSendAppInfoToProvider(true)
+                        .build(),
+                "com.google.android.youtube"
         )
     }
 
     private fun testCreatePasswordRequestInfo(): RequestInfo {
         val request = CreatePasswordRequest("beckett-bakert@gmail.com", "password123")
         return RequestInfo.newCreateRequestInfo(
-            Binder(),
-            CreateCredentialRequest(
-                TYPE_PASSWORD_CREDENTIAL,
-                request.credentialData,
-                request.candidateQueryData,
-                /*isSystemProviderRequired=*/ false,
-                /*alwaysSendAppInfoToProvider=*/ true
-            ),
-            "com.google.android.youtube"
+                Binder(),
+                CreateCredentialRequest.Builder(request.credentialData, request.candidateQueryData)
+                        .setType(TYPE_PASSWORD_CREDENTIAL)
+                        .setIsSystemProviderRequired(false)
+                        .setAlwaysSendAppInfoToProvider(true)
+                        .build(),
+                "com.google.android.youtube"
         )
     }
 
@@ -437,15 +433,13 @@
             displayInfo.toBundle()
         )
         return RequestInfo.newCreateRequestInfo(
-            Binder(),
-            CreateCredentialRequest(
-                "other-sign-ins",
-                data,
-                /*candidateQueryData=*/ Bundle(),
-                /*isSystemProviderRequired=*/ false,
-                /*alwaysSendAppInfoToProvider=*/ true
-            ),
-            "com.google.android.youtube"
+                Binder(),
+                CreateCredentialRequest.Builder(data, Bundle())
+                        .setType("other-sign-ins")
+                        .setIsSystemProviderRequired(false)
+                        .setAlwaysSendAppInfoToProvider(true)
+                        .build(),
+                "com.google.android.youtube"
         )
     }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
new file mode 100644
index 0000000..ee8cffe
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.android.credentialmanager.common.Constants
+
+
+class CredentialProviderReceiver : BroadcastReceiver() {
+
+    override fun onReceive(context: Context?, intent: Intent?) {
+        Log.d(Constants.LOG_TAG, "Received intent in CredentialProviderReceiver")
+
+        val sharedPreferences = context?.getSharedPreferences(context?.packageName,
+                Context.MODE_PRIVATE)
+        sharedPreferences?.edit()?.remove(UserConfigRepo.DEFAULT_PROVIDER)?.commit()
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
index d1dceb3..5396de0 100644
--- a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
+++ b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
@@ -17,6 +17,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.settingslib.spaprivileged">
-<uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
 </manifest>
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 171903f..c609004 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -16,11 +16,14 @@
 
 package com.android.settingslib.spaprivileged.model.app
 
+import android.app.AppOpsManager;
 import android.app.AppOpsManager.MODE_ALLOWED
 import android.app.AppOpsManager.MODE_ERRORED
 import android.app.AppOpsManager.Mode
 import android.content.Context
 import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.UserHandle
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.map
@@ -44,17 +47,25 @@
     private val setModeByUid: Boolean = false,
 ) : IAppOpsController {
     private val appOpsManager = context.appOpsManager
+    private val packageManager = context.packageManager
 
     override val mode: LiveData<Int>
         get() = _mode
 
     override fun setAllowed(allowed: Boolean) {
         val mode = if (allowed) MODE_ALLOWED else modeForNotAllowed
+
         if (setModeByUid) {
             appOpsManager.setUidMode(op, app.uid, mode)
         } else {
             appOpsManager.setMode(op, app.uid, app.packageName, mode)
         }
+
+        val permission = AppOpsManager.opToPermission(op)
+        packageManager.updatePermissionFlags(permission, app.packageName,
+                PackageManager.FLAG_PERMISSION_USER_SET, PackageManager.FLAG_PERMISSION_USER_SET,
+                UserHandle.getUserHandleForUid(app.uid))
+
         _mode.postValue(mode)
     }
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
index fd2ceb7..23270c1 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
@@ -22,6 +22,7 @@
 import android.app.AppOpsManager.MODE_IGNORED
 import android.content.Context
 import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spaprivileged.framework.common.appOpsManager
@@ -31,6 +32,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.Spy
@@ -45,9 +50,13 @@
 
     @Mock private lateinit var appOpsManager: AppOpsManager
 
+    @Mock private lateinit var packageManager: PackageManager
+
     @Before
     fun setUp() {
         whenever(context.appOpsManager).thenReturn(appOpsManager)
+        doNothing().`when`(packageManager)
+                .updatePermissionFlags(anyString(), anyString(), anyInt(), anyInt(), any())
     }
 
     @Test
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc012/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc012/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc012/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc590/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc590/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc590/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc591/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc591/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc591/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc592/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc592/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc592/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc593/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc593/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc593/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc594/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc594/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc594/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc595/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc595/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc595/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc596/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc596/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc596/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc597/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc597/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc597/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc598/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc598/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc598/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc599/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc599/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc599/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc890/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc890/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc890/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc270/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc270/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc270/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc280/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc280/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc280/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc281/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc281/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc281/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc282/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc282/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc282/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc283/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc283/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc283/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc284/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc284/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc284/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc285/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc285/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc285/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc286/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc286/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc286/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc287/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc287/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc287/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc288/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc288/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc288/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc289/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc289/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc289/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc480/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc480/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc480/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc481/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc481/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc481/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc482/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc482/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc482/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc483/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc483/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc483/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc484/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc484/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc484/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc485/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc485/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc485/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc486/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc486/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc486/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc487/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc487/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc487/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc488/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc488/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc488/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc489/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc489/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc489/ic_5g_plus_mobiledata.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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+      <path android:fillColor="#FF000000"
+            android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+    </group>
+    <group>
+      <path android:fillColor="#FF000000"
+          android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+    </group>
+    <path android:fillColor="#FF000000"
+        android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc012/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc012/strings.xml
new file mode 100644
index 0000000..aa504ef
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc012/strings.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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
+
diff --git a/packages/SettingsLib/res/values-mcc310-mnc590/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc590/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc590/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc591/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc591/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc591/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc592/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc592/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc592/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc593/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc593/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc593/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc594/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc594/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc594/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc595/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc595/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc595/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc596/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc596/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc596/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc597/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc597/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc597/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc598/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc598/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc598/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc599/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc599/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc599/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc890/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc890/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc890/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc270/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc270/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc270/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc280/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc280/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc280/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc281/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc281/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc281/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc282/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc282/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc282/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc283/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc283/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc283/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc284/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc284/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc284/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc285/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc285/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc285/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc286/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc286/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc286/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc287/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc287/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc287/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc288/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc288/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc288/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc289/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc289/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc289/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc481/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc481/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc481/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc482/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc482/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc482/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc483/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc483/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc483/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc484/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc484/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc484/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc485/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc485/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc485/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc486/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc486/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc486/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc487/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc487/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc487/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc488/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc488/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc488/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc489/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc489/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc489/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 337c251..5022960 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -118,9 +118,9 @@
 
     <!-- Titles for Bluetooth HCI Snoop Filtered Logging -->
     <string-array name="bt_hci_snoop_log_filters_entries">
-        <item>Headers Filtered</item>
-        <item>A2DP Media Packets Filtered</item>
-        <item>RFCOMM Channel Filtered</item>
+        <item>Leave only ACL headers</item>
+        <item>Filter A2DP media packets</item>
+        <item>Filter RFCOMM channel</item>
     </string-array>
 
         <!-- Values for Bluetooth HCI Snoop Filtered Logging -->
@@ -132,10 +132,10 @@
 
         <!-- Titles for Bluetooth HCI Snoop Filtered Logging -->
     <string-array name="bt_hci_snoop_log_profile_filter_entries">
-        <item>Disabled</item>
-        <item>Magic</item>
-        <item>Header</item>
-        <item>Full Filter</item>
+        <item>Disable</item>
+        <item>Fill with string of characters</item>
+        <item>Leave only header</item>
+        <item>Fully remove</item>
     </string-array>
 
         <!-- Values for Bluetooth HCI Snoop Filtered Logging -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index 85e8aad..59735f41 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -183,7 +183,7 @@
         final String currentShortcutServiceId = Settings.Secure.getStringForUser(
                 context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
                 userId);
-        if (!TextUtils.isEmpty(currentShortcutServiceId)) {
+        if (currentShortcutServiceId != null) {
             return currentShortcutServiceId;
         }
         return context.getString(R.string.config_defaultAccessibilityService);
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 1ac20471..346462d 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -48,6 +48,7 @@
         "test/**/*.java",
         "src/android/provider/settings/backup/*",
         "src/android/provider/settings/validators/*",
+        "src/com/android/providers/settings/GenerationRegistry.java",
         "src/com/android/providers/settings/SettingsBackupAgent.java",
         "src/com/android/providers/settings/SettingsState.java",
         "src/com/android/providers/settings/SettingsHelper.java",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 5617331..7f3b0ff 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -17,19 +17,19 @@
 package com.android.providers.settings;
 
 import android.os.Bundle;
-import android.os.UserManager;
 import android.provider.Settings;
+import android.util.ArrayMap;
 import android.util.MemoryIntArray;
 import android.util.Slog;
-import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
 
 /**
  * This class tracks changes for config/global/secure/system tables
- * on a per user basis and updates a shared memory region which
+ * on a per user basis and updates shared memory regions which
  * client processes can read to determine if their local caches are
  * stale.
  */
@@ -40,138 +40,187 @@
 
     private final Object mLock;
 
+    // Key -> backingStore mapping
     @GuardedBy("mLock")
-    private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
+    private final ArrayMap<Integer, MemoryIntArray> mKeyToBackingStoreMap = new ArrayMap();
+
+    // Key -> (String->Index map) mapping
+    @GuardedBy("mLock")
+    private final ArrayMap<Integer, ArrayMap<String, Integer>> mKeyToIndexMapMap = new ArrayMap<>();
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    // Maximum number of backing stores allowed
+    static final int NUM_MAX_BACKING_STORE = 8;
 
     @GuardedBy("mLock")
-    private MemoryIntArray mBackingStore;
+    private int mNumBackingStore = 0;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    // Maximum size of an individual backing store
+    static final int MAX_BACKING_STORE_SIZE = MemoryIntArray.getMaxSize();
 
     public GenerationRegistry(Object lock) {
         mLock = lock;
     }
 
-    public void incrementGeneration(int key) {
+    /**
+     *  Increment the generation number if the setting is already cached in the backing stores.
+     *  Otherwise, do nothing.
+     */
+    public void incrementGeneration(int key, String name) {
+        final boolean isConfig =
+                (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
+        // Only store the prefix if the mutated setting is a config
+        final String indexMapKey = isConfig ? (name.split("/")[0] + "/") : name;
         synchronized (mLock) {
-            MemoryIntArray backingStore = getBackingStoreLocked();
-            if (backingStore != null) {
-                try {
-                    final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
-                    if (index >= 0) {
-                        final int generation = backingStore.get(index) + 1;
-                        backingStore.set(index, generation);
-                    }
-                } catch (IOException e) {
-                    Slog.e(LOG_TAG, "Error updating generation id", e);
-                    destroyBackingStore();
+            final MemoryIntArray backingStore = getBackingStoreLocked(key,
+                    /* createIfNotExist= */ false);
+            if (backingStore == null) {
+                return;
+            }
+            try {
+                final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
+                        backingStore, /* createIfNotExist= */ false);
+                if (index < 0) {
+                    return;
                 }
+                final int generation = backingStore.get(index) + 1;
+                backingStore.set(index, generation);
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "Incremented generation for setting:" + indexMapKey
+                            + " key:" + SettingsState.keyToString(key)
+                            + " at index:" + index);
+                }
+            } catch (IOException e) {
+                Slog.e(LOG_TAG, "Error updating generation id", e);
+                destroyBackingStoreLocked(key);
             }
         }
     }
 
-    public void addGenerationData(Bundle bundle, int key) {
+    /**
+     *  Return the backing store's reference, the index and the current generation number
+     *  of a cached setting. If it was not in the backing store, first create the entry in it before
+     *  returning the result.
+     */
+    public void addGenerationData(Bundle bundle, int key, String indexMapKey) {
         synchronized (mLock) {
-            MemoryIntArray backingStore = getBackingStoreLocked();
+            final MemoryIntArray backingStore = getBackingStoreLocked(key,
+                    /* createIfNotExist= */ true);
+            if (backingStore == null) {
+                // Error accessing existing backing store or no new backing store is available
+                return;
+            }
             try {
-                if (backingStore != null) {
-                    final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
-                    if (index >= 0) {
-                        bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
-                                backingStore);
-                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
-                        bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
-                                backingStore.get(index));
-                        if (DEBUG) {
-                            Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
-                                    + SettingsProvider.keyToString(key));
-                        }
-                    }
+                final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
+                        backingStore, /* createIfNotExist= */ true);
+                if (index < 0) {
+                    // Should not happen unless having error accessing the backing store
+                    return;
+                }
+                bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
+                        backingStore);
+                bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
+                bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
+                        backingStore.get(index));
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "Exported index:" + index
+                            + " for setting:" + indexMapKey
+                            + " key:" + SettingsState.keyToString(key));
                 }
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "Error adding generation data", e);
-                destroyBackingStore();
+                destroyBackingStoreLocked(key);
             }
         }
     }
 
     public void onUserRemoved(int userId) {
+        final int secureKey = SettingsState.makeKey(
+                SettingsState.SETTINGS_TYPE_SECURE, userId);
+        final int systemKey = SettingsState.makeKey(
+                SettingsState.SETTINGS_TYPE_SYSTEM, userId);
         synchronized (mLock) {
-            MemoryIntArray backingStore = getBackingStoreLocked();
-            if (backingStore != null && mKeyToIndexMap.size() > 0) {
-                try {
-                    final int secureKey = SettingsProvider.makeKey(
-                            SettingsProvider.SETTINGS_TYPE_SECURE, userId);
-                    resetSlotForKeyLocked(secureKey, mKeyToIndexMap, backingStore);
-
-                    final int systemKey = SettingsProvider.makeKey(
-                            SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
-                    resetSlotForKeyLocked(systemKey, mKeyToIndexMap, backingStore);
-                } catch (IOException e) {
-                    Slog.e(LOG_TAG, "Error cleaning up for user", e);
-                    destroyBackingStore();
-                }
+            if (mKeyToIndexMapMap.containsKey(secureKey)) {
+                destroyBackingStoreLocked(secureKey);
+                mKeyToIndexMapMap.remove(secureKey);
+                mNumBackingStore = mNumBackingStore - 1;
+            }
+            if (mKeyToIndexMapMap.containsKey(systemKey)) {
+                destroyBackingStoreLocked(systemKey);
+                mKeyToIndexMapMap.remove(systemKey);
+                mNumBackingStore = mNumBackingStore - 1;
             }
         }
     }
 
     @GuardedBy("mLock")
-    private MemoryIntArray getBackingStoreLocked() {
-        if (mBackingStore == null) {
-            // One for the config table, one for the global table, two for system
-            // and secure tables for a managed profile (managed profile is not
-            // included in the max user count), ten for partially deleted users if
-            // users are quickly removed, and twice max user count for system and
-            // secure.
-            final int size = 1 + 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
+    private MemoryIntArray getBackingStoreLocked(int key, boolean createIfNotExist) {
+        MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
+        if (!createIfNotExist) {
+            return backingStore;
+        }
+        if (backingStore == null) {
             try {
-                mBackingStore = new MemoryIntArray(size);
+                if (mNumBackingStore >= NUM_MAX_BACKING_STORE) {
+                    Slog.e(LOG_TAG, "Error creating backing store - at capacity");
+                    return null;
+                }
+                backingStore = new MemoryIntArray(MAX_BACKING_STORE_SIZE);
+                mKeyToBackingStoreMap.put(key, backingStore);
+                mNumBackingStore += 1;
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Created backing store " + mBackingStore);
+                    Slog.e(LOG_TAG, "Created backing store for "
+                            + SettingsState.keyToString(key) + " on user: "
+                            + SettingsState.getUserIdFromKey(key));
                 }
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "Error creating generation tracker", e);
             }
         }
-        return mBackingStore;
+        return backingStore;
     }
 
-    private void destroyBackingStore() {
-        if (mBackingStore != null) {
+    @GuardedBy("mLock")
+    private void destroyBackingStoreLocked(int key) {
+        MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
+        if (backingStore != null) {
             try {
-                mBackingStore.close();
+                backingStore.close();
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Destroyed backing store " + mBackingStore);
+                    Slog.e(LOG_TAG, "Destroyed backing store " + backingStore);
                 }
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "Cannot close generation memory array", e);
             }
-            mBackingStore = null;
+            mKeyToBackingStoreMap.remove(key);
         }
     }
 
-    private static void resetSlotForKeyLocked(int key, SparseIntArray keyToIndexMap,
-            MemoryIntArray backingStore) throws IOException {
-        final int index = keyToIndexMap.get(key, -1);
-        if (index >= 0) {
-            keyToIndexMap.delete(key);
-            backingStore.set(index, 0);
-            if (DEBUG) {
-                Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
-                        + SettingsProvider.keyToString(key));
+    private static int getKeyIndexLocked(int key, String indexMapKey,
+            ArrayMap<Integer, ArrayMap<String, Integer>> keyToIndexMapMap,
+            MemoryIntArray backingStore, boolean createIfNotExist) throws IOException {
+        ArrayMap<String, Integer> nameToIndexMap = keyToIndexMapMap.get(key);
+        if (nameToIndexMap == null) {
+            if (!createIfNotExist) {
+                return -1;
             }
+            nameToIndexMap = new ArrayMap<>();
+            keyToIndexMapMap.put(key, nameToIndexMap);
         }
-    }
-
-    private static int getKeyIndexLocked(int key, SparseIntArray keyToIndexMap,
-            MemoryIntArray backingStore) throws IOException {
-        int index = keyToIndexMap.get(key, -1);
+        int index = nameToIndexMap.getOrDefault(indexMapKey, -1);
         if (index < 0) {
+            if (!createIfNotExist) {
+                return -1;
+            }
             index = findNextEmptyIndex(backingStore);
             if (index >= 0) {
                 backingStore.set(index, 1);
-                keyToIndexMap.append(key, index);
+                nameToIndexMap.put(indexMapKey, index);
                 if (DEBUG) {
-                    Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
-                            + SettingsProvider.keyToString(key));
+                    Slog.i(LOG_TAG, "Allocated index:" + index + " for setting:" + indexMapKey
+                            + " of type:" + SettingsState.keyToString(key)
+                            + " on user:" + SettingsState.getUserIdFromKey(key));
                 }
             } else {
                 Slog.e(LOG_TAG, "Could not allocate generation index");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 683c08e..d7c7130 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -35,6 +35,7 @@
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
 import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
+import static com.android.providers.settings.SettingsState.makeKey;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -375,10 +376,6 @@
     @GuardedBy("mLock")
     private boolean mSyncConfigDisabledUntilReboot;
 
-    public static int makeKey(int type, int userId) {
-        return SettingsState.makeKey(type, userId);
-    }
-
     public static int getTypeFromKey(int key) {
         return SettingsState.getTypeFromKey(key);
     }
@@ -387,9 +384,6 @@
         return SettingsState.getUserIdFromKey(key);
     }
 
-    public static String keyToString(int key) {
-        return SettingsState.keyToString(key);
-    }
     @ChangeId
     @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
     private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
@@ -553,7 +547,7 @@
 
             case Settings.CALL_METHOD_LIST_CONFIG: {
                 String prefix = getSettingPrefix(args);
-                Bundle result = packageValuesForCallResult(getAllConfigFlags(prefix),
+                Bundle result = packageValuesForCallResult(prefix, getAllConfigFlags(prefix),
                         isTrackingGeneration(args));
                 reportDeviceConfigAccess(prefix);
                 return result;
@@ -1319,6 +1313,7 @@
         return false;
     }
 
+    @NonNull
     private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
@@ -2316,7 +2311,8 @@
                 "get/set setting for user", null);
     }
 
-    private Bundle packageValueForCallResult(Setting setting, boolean trackingGeneration) {
+    private Bundle packageValueForCallResult(@Nullable Setting setting,
+            boolean trackingGeneration) {
         if (!trackingGeneration) {
             if (setting == null || setting.isNull()) {
                 return NULL_SETTING_BUNDLE;
@@ -2327,19 +2323,26 @@
         result.putString(Settings.NameValueTable.VALUE,
                 !setting.isNull() ? setting.getValue() : null);
 
-        mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey());
+        if (setting != null && !setting.isNull()) {
+            // No need to track generation if the setting doesn't exist
+            synchronized (mLock) {
+                mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey(),
+                        setting.getName());
+            }
+        }
         return result;
     }
 
-    private Bundle packageValuesForCallResult(HashMap<String, String> keyValues,
-            boolean trackingGeneration) {
+    private Bundle packageValuesForCallResult(String prefix,
+            @NonNull HashMap<String, String> keyValues, boolean trackingGeneration) {
         Bundle result = new Bundle();
         result.putSerializable(Settings.NameValueTable.VALUE, keyValues);
-        if (trackingGeneration) {
+        if (trackingGeneration && !keyValues.isEmpty()) {
+            // No need to track generation if the namespace is empty
             synchronized (mLock) {
                 mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
-                        mSettingsRegistry.getSettingsLocked(
-                                SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM).mKey);
+                        mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG,
+                                UserHandle.USER_SYSTEM).mKey, prefix);
             }
         }
 
@@ -3449,7 +3452,7 @@
 
         private void notifyForSettingsChange(int key, String name) {
             // Increment the generation first, so observers always see the new value
-            mGenerationRegistry.incrementGeneration(key);
+            mGenerationRegistry.incrementGeneration(key, name);
 
             if (isGlobalSettingsKey(key) || isConfigSettingsKey(key)) {
                 final long token = Binder.clearCallingIdentity();
@@ -3487,7 +3490,7 @@
                 List<String> changedSettings) {
 
             // Increment the generation first, so observers always see the new value
-            mGenerationRegistry.incrementGeneration(key);
+            mGenerationRegistry.incrementGeneration(key, prefix);
 
             StringBuilder stringBuilder = new StringBuilder(prefix);
             for (int i = 0; i < changedSettings.size(); ++i) {
@@ -3513,7 +3516,7 @@
                     if (profileId != userId) {
                         final int key = makeKey(type, profileId);
                         // Increment the generation first, so observers always see the new value
-                        mGenerationRegistry.incrementGeneration(key);
+                        mGenerationRegistry.incrementGeneration(key, name);
                         mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
                                 profileId, 0, uri).sendToTarget();
                     }
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
new file mode 100644
index 0000000..d34fe694
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.providers.settings;
+
+import static android.provider.Settings.CALL_METHOD_GENERATION_INDEX_KEY;
+import static android.provider.Settings.CALL_METHOD_GENERATION_KEY;
+import static android.provider.Settings.CALL_METHOD_TRACK_GENERATION_KEY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.util.MemoryIntArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+public class GenerationRegistryTest {
+    @Test
+    public void testGenerationsWithRegularSetting() throws IOException {
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
+        final String testSecureSetting = "test_secure_setting";
+        Bundle b = new Bundle();
+        // IncrementGeneration should have no effect on a non-cached setting.
+        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+        // Default index is 0 and generation is only 1 despite early calls of incrementGeneration
+        checkBundle(b, 0, 1, false);
+
+        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+        // Index is still 0 and generation is now 2; also check direct array access
+        assertThat(getArray(b).get(0)).isEqualTo(2);
+
+        final int systemKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 0);
+        final String testSystemSetting = "test_system_setting";
+        generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
+        // Default index is 0 and generation is 1 for another backingStore (system)
+        checkBundle(b, 0, 1, false);
+
+        final String testSystemSetting2 = "test_system_setting2";
+        generationRegistry.addGenerationData(b, systemKey, testSystemSetting2);
+        // Second system setting index is 1 and default generation is 1
+        checkBundle(b, 1, 1, false);
+
+        generationRegistry.incrementGeneration(systemKey, testSystemSetting);
+        generationRegistry.incrementGeneration(systemKey, testSystemSetting);
+        generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
+        // First system setting generation now incremented to 3
+        checkBundle(b, 0, 3, false);
+
+        final int systemKey2 = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 10);
+        generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
+        // User 10 has a new set of backingStores
+        checkBundle(b, 0, 1, false);
+
+        // Check user removal
+        generationRegistry.onUserRemoved(10);
+        generationRegistry.incrementGeneration(systemKey2, testSystemSetting);
+
+        // Removed user should not affect existing caches
+        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+        assertThat(getArray(b).get(0)).isEqualTo(2);
+
+        // IncrementGeneration should have no effect for a non-cached user
+        b.clear();
+        checkBundle(b, -1, -1, true);
+        // AddGeneration should create new backing store for the non-cached user
+        generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
+        checkBundle(b, 0, 1, false);
+    }
+
+    @Test
+    public void testGenerationsWithConfigSetting() throws IOException {
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final String prefix = "test_namespace/";
+        final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+
+        Bundle b = new Bundle();
+        generationRegistry.addGenerationData(b, configKey, prefix);
+        checkBundle(b, 0, 1, false);
+
+        final String setting = "test_namespace/test_setting";
+        // Check that the generation of the prefix is incremented correctly
+        generationRegistry.incrementGeneration(configKey, setting);
+        generationRegistry.addGenerationData(b, configKey, prefix);
+        checkBundle(b, 0, 2, false);
+    }
+
+    @Test
+    public void testMaxNumBackingStores() throws IOException {
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final String testSecureSetting = "test_secure_setting";
+        Bundle b = new Bundle();
+        for (int i = 0; i < GenerationRegistry.NUM_MAX_BACKING_STORE; i++) {
+            b.clear();
+            final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, i);
+            generationRegistry.addGenerationData(b, key, testSecureSetting);
+            checkBundle(b, 0, 1, false);
+        }
+        b.clear();
+        final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE,
+                GenerationRegistry.NUM_MAX_BACKING_STORE + 1);
+        generationRegistry.addGenerationData(b, key, testSecureSetting);
+        // Should fail to add generation because the number of backing stores has reached limit
+        checkBundle(b, -1, -1, true);
+        // Remove one user should free up a backing store
+        generationRegistry.onUserRemoved(0);
+        generationRegistry.addGenerationData(b, key, testSecureSetting);
+        checkBundle(b, 0, 1, false);
+    }
+
+    @Test
+    public void testMaxSizeBackingStore() throws IOException {
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
+        final String testSecureSetting = "test_secure_setting";
+        Bundle b = new Bundle();
+        for (int i = 0; i < GenerationRegistry.MAX_BACKING_STORE_SIZE; i++) {
+            generationRegistry.addGenerationData(b, secureKey, testSecureSetting + i);
+            checkBundle(b, i, 1, false);
+        }
+        b.clear();
+        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+        // Should fail to increase index because the number of entries in the backing store has
+        // reached the limit
+        checkBundle(b, -1, -1, true);
+        // Shouldn't affect other cached entries
+        generationRegistry.addGenerationData(b, secureKey, testSecureSetting + "0");
+        checkBundle(b, 0, 1, false);
+    }
+
+    private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
+            throws IOException {
+        final MemoryIntArray array = getArray(b);
+        if (isNull) {
+            assertThat(array).isNull();
+        } else {
+            assertThat(array).isNotNull();
+        }
+        final int index = b.getInt(
+                CALL_METHOD_GENERATION_INDEX_KEY, -1);
+        assertThat(index).isEqualTo(expectedIndex);
+        final int generation = b.getInt(CALL_METHOD_GENERATION_KEY, -1);
+        assertThat(generation).isEqualTo(expectedGeneration);
+        if (!isNull) {
+            // Read into the result array with the result index should match the result generation
+            assertThat(array.get(index)).isEqualTo(generation);
+        }
+    }
+
+    private MemoryIntArray getArray(Bundle b) {
+        return b.getParcelable(
+                CALL_METHOD_TRACK_GENERATION_KEY, android.util.MemoryIntArray.class);
+    }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 82ca63d..596ff0e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -460,6 +460,9 @@
     <!-- Permission needed to test registering pull atom callbacks -->
     <uses-permission android:name="android.permission.REGISTER_STATS_PULL_ATOM" />
 
+    <!-- Permission needed to test querying restricted metrics -->
+    <uses-permission android:name="android.permission.READ_RESTRICTED_STATS" />
+
     <!-- Permission needed to modify settings overrideable by restore in CTS tests -->
     <uses-permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE" />
 
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 40af294..c1f2aa8 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
@@ -275,7 +275,6 @@
                 info.brightnessMinimum,
                 info.brightnessMaximum
         );
-        mDisplayManager.setTemporaryBrightness(getDisplayId(), brightness);
         mDisplayManager.setBrightness(getDisplayId(), brightness);
         mA11yMenuLayout.showSnackbar(
                 getString(R.string.brightness_percentage_label,
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index dd0a4f0..a25790a 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility.accessibilitymenu.view;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static java.lang.Math.max;
 
 import android.animation.Animator;
@@ -133,7 +135,12 @@
             initLayoutParams();
         }
 
-        mLayout = new FrameLayout(mService);
+        final Display display = mService.getSystemService(
+                DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+
+        mLayout = new FrameLayout(
+                mService.createDisplayContext(display).createWindowContext(
+                        WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, null));
         updateLayoutPosition();
         inflateLayoutAndSetOnTouchListener(mLayout);
         mA11yMenuViewPager = new A11yMenuViewPager(mService);
diff --git a/packages/SystemUI/res/drawable/statusbar_chip_bg.xml b/packages/SystemUI/res/drawable/statusbar_chip_bg.xml
new file mode 100644
index 0000000..d7de16d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/statusbar_chip_bg.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"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <solid android:color="?androidprv:attr/colorAccentPrimary" />
+    <corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/statusbar_privacy_chip_bg.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/privacy_chip_bg.xml
rename to packages/SystemUI/res/drawable/statusbar_privacy_chip_bg.xml
diff --git a/packages/SystemUI/res/layout/battery_status_chip.xml b/packages/SystemUI/res/layout/battery_status_chip.xml
new file mode 100644
index 0000000..ff68ac0
--- /dev/null
+++ b/packages/SystemUI/res/layout/battery_status_chip.xml
@@ -0,0 +1,43 @@
+<?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:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_vertical|end"
+    tools:parentTag="com.android.systemui.statusbar.BatteryStatusChip">
+
+    <LinearLayout
+        android:id="@+id/rounded_container"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/ongoing_appops_chip_height"
+        android:layout_gravity="center"
+        android:background="@drawable/statusbar_chip_bg"
+        android:clipToOutline="true"
+        android:gravity="center"
+        android:maxWidth="@dimen/ongoing_appops_chip_max_width"
+        android:minWidth="@dimen/ongoing_appops_chip_min_width">
+
+        <com.android.systemui.battery.BatteryMeterView
+            android:id="@+id/battery_meter_view"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="10dp" />
+
+    </LinearLayout>
+</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index d689828..dffe40b 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -141,11 +141,14 @@
         android:layout_width="wrap_content"
         android:layout_height="@dimen/large_screen_shade_header_min_height"
         android:gravity="center"
-        app:layout_constraintEnd_toEndOf="@id/end_guide"
-        app:layout_constraintTop_toTopOf="@id/date"
         app:layout_constraintBottom_toBottomOf="@id/date"
-        >
-        <include layout="@layout/ongoing_privacy_chip"/>
+        app:layout_constraintEnd_toEndOf="@id/end_guide"
+        app:layout_constraintTop_toTopOf="@id/date">
+
+        <com.android.systemui.privacy.OngoingPrivacyChip
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent" />
+
     </FrameLayout>
 
 </com.android.systemui.util.NoRemeasureMotionLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index 8ba1ff3..37b8ae0 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -76,7 +76,10 @@
                     android:layout_height="48dp"
                     android:layout_width="wrap_content"
                     android:layout_gravity="center_vertical"
-                    android:padding="8dp" />
+                    android:padding="8dp"
+                    android:track="@drawable/settingslib_track_selector"
+                    android:thumb="@drawable/settingslib_thumb_selector"
+                    android:theme="@style/MainSwitch.Settingslib"/>
             </com.android.systemui.statusbar.notification.row.AppControlView>
 
             <!-- ChannelRows get added dynamically -->
@@ -101,7 +104,7 @@
                 android:minWidth="@dimen/notification_importance_toggle_size"
                 android:minHeight="@dimen/notification_importance_toggle_size"
                 android:maxWidth="200dp"
-                style="@style/TextAppearance.NotificationInfo.Button"/>
+                style="@style/Widget.Dialog.Button"/>
             <TextView
                 android:id="@+id/done_button"
                 android:text="@string/inline_ok_button"
@@ -113,7 +116,7 @@
                 android:minWidth="@dimen/notification_importance_toggle_size"
                 android:minHeight="@dimen/notification_importance_toggle_size"
                 android:layout_alignParentEnd="true"
-                style="@style/TextAppearance.NotificationInfo.Button"/>
+                style="@style/Widget.Dialog.Button"/>
         </RelativeLayout>
     </LinearLayout>
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
index d03cd7e..190f999 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
@@ -85,6 +85,9 @@
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical"
             android:padding="8dp"
+            android:track="@drawable/settingslib_track_selector"
+            android:thumb="@drawable/settingslib_thumb_selector"
+            android:theme="@style/MainSwitch.Settingslib"
         />
     </LinearLayout>
 </com.android.systemui.statusbar.notification.row.ChannelRow>
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index d1a2cf4..2c7467d 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -16,16 +16,15 @@
 -->
 
 
-<com.android.systemui.privacy.OngoingPrivacyChip
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/privacy_chip"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_height="match_parent"
     android:layout_width="wrap_content"
     android:layout_gravity="center_vertical|end"
-    android:focusable="true"
     android:clipChildren="false"
     android:clipToPadding="false"
-    android:paddingStart="8dp"
+    tools:parentTag="com.android.systemui.privacy.OngoingPrivacyChip">
     >
 
         <LinearLayout
@@ -35,8 +34,9 @@
             android:paddingStart="10dp"
             android:paddingEnd="10dp"
             android:gravity="center"
+            android:clipToOutline="true"
+            android:clipToPadding="false"
             android:layout_gravity="center"
             android:minWidth="@dimen/ongoing_appops_chip_min_width"
-            android:maxWidth="@dimen/ongoing_appops_chip_max_width"
-            />
-</com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file
+            android:maxWidth="@dimen/ongoing_appops_chip_max_width" />
+</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json
new file mode 100644
index 0000000..49c1c40
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Landscape_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":13,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-129,"s":[-67]},{"t":-29,"s":[0]}],"ix":10},"p":{"a":0,"k":[-75.352,41.307,0],"ix":2,"l":2},"a":{"a":0,"k":[94.648,211.307,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.72,-5.642],[0,0],[-9.394,-0.562],[-0.298,-0.038]],"o":[[-5.153,4.329],[3.882,-16.05],[0.31,0.019],[-0.044,0.75]],"v":[[0.863,12.222],[-8.931,14.755],[8.005,-15.108],[8.931,-15.021]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.486,130.081],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.459,6.045],[-5.153,4.329],[-0.044,0.75],[3.116,-24.664],[5.23,-22.052],[8.666,11.92],[-2.9,9.135]],"o":[[0,0],[6.72,-5.642],[12.723,1.335],[-2.369,18.762],[-13.993,-5.333],[2.255,-5.502],[1.843,-5.815]],"v":[[-9.99,-18.348],[-0.196,-20.881],[7.872,-48.124],[21.578,-9.331],[12.104,48.124],[-22.574,21.555],[-14.791,-0.206]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82.545,163.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"black circle matte 4","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"black circle matte 5","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey700","cl":"grey700","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json
new file mode 100644
index 0000000..9ea0d35
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Portrait_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 3","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-2,"ix":10},"p":{"a":0,"k":[260.134,83.782,0],"ix":2,"l":2},"a":{"a":0,"k":[302.634,38.782,0],"ix":1,"l":2},"s":{"a":0,"k":[178,178,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.262,5.076],[0,0],[-0.424,-7.095],[-0.028,-0.225]],"o":[[3.269,-3.892],[-12.123,2.932],[0.015,0.234],[0.567,-0.034]],"v":[[9.232,0.652],[11.145,-6.746],[-11.412,6.046],[-11.346,6.746]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[241.281,55.033],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.565,-1.102],[3.269,-3.892],[0.566,-0.033],[-18.63,2.353],[-16.656,3.951],[9.004,6.546],[6.9,-2.19]],"o":[[0,0],[-4.262,5.076],[1.008,9.61],[14.171,-1.79],[-4.028,-10.569],[-4.156,1.703],[-4.392,1.392]],"v":[[-13.858,-7.546],[-15.771,-0.148],[-36.349,5.946],[-7.047,16.299],[36.349,9.142],[16.281,-17.051],[-0.156,-11.172]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[266.285,55.833],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"black circle matte 4","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"black circle matte 5","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey700","cl":"grey700","parent":16,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json
new file mode 100644
index 0000000..f2b2593
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Portrait_Reverse_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":13,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-75.352,41.307,0],"ix":2,"l":2},"a":{"a":0,"k":[94.648,211.307,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.72,-5.642],[0,0],[-9.394,-0.562],[-0.298,-0.038]],"o":[[-5.153,4.329],[3.882,-16.05],[0.31,0.019],[-0.044,0.75]],"v":[[0.863,12.222],[-8.931,14.755],[8.005,-15.108],[8.931,-15.021]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.486,130.081],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.459,6.045],[-5.153,4.329],[-0.044,0.75],[3.116,-24.664],[5.23,-22.052],[8.666,11.92],[-2.9,9.135]],"o":[[0,0],[6.72,-5.642],[12.723,1.335],[-2.369,18.762],[-13.993,-5.333],[2.255,-5.502],[1.843,-5.815]],"v":[[-9.99,-18.348],[-0.196,-20.881],[7.872,-48.124],[21.578,-9.331],[12.104,48.124],[-22.574,21.555],[-14.791,-0.206]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82.545,163.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"black circle matte 4","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"black circle matte 5","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey700","cl":"grey700","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3ebaae0..7b3caff 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1721,13 +1721,13 @@
     <string name="keyboard_shortcut_search_list_hint">Search shortcuts</string>
     <!-- The description for no shortcuts results [CHAR LIMIT=25] -->
     <string name="keyboard_shortcut_search_list_no_result">No shortcuts found</string>
-    <!-- The title of system category in shortcut search list. [CHAR LIMIT=15] -->
+    <!-- The title of system category in shortcut search list. [CHAR LIMIT=25] -->
     <string name="keyboard_shortcut_search_category_system">System</string>
-    <!-- The title of input category in shortcut search list. [CHAR LIMIT=15] -->
+    <!-- The title of input category in shortcut search list. [CHAR LIMIT=25] -->
     <string name="keyboard_shortcut_search_category_input">Input</string>
-    <!-- The title of open apps category in shortcut search list. [CHAR LIMIT=15] -->
+    <!-- The title of open apps category in shortcut search list. [CHAR LIMIT=25] -->
     <string name="keyboard_shortcut_search_category_open_apps">Open apps</string>
-    <!-- The title of current app category in shortcut search list. [CHAR LIMIT=15] -->
+    <!-- The title of current app category in shortcut search list. [CHAR LIMIT=25] -->
     <string name="keyboard_shortcut_search_category_current_app">Current app</string>
 
     <!-- User visible title for the keyboard shortcut that triggers the notification shade. [CHAR LIMIT=70] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index a71fb56..4bc9491 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -104,4 +104,9 @@
       * Sent when the surface for navigation bar is created or changed
       */
      void onNavigationBarSurface(in SurfaceControl surface) = 26;
+
+     /**
+      * Sent when the task bar stash state is toggled.
+      */
+     void onTaskbarToggled() = 27;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 2a37bd3..80b9758 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -36,7 +36,6 @@
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.IRecentsAnimationController;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -106,12 +105,24 @@
         private RecentsAnimationListener mListener = null;
         private RecentsAnimationControllerCompat mWrapped = null;
         private IRemoteTransitionFinishedCallback mFinishCB = null;
+
+        /**
+         * List of tasks that we are switching away from via this transition. Upon finish, these
+         * pausing tasks will become invisible.
+         * These need to be ordered since the order must be restored if there is no task-switch.
+         */
         private ArrayList<TaskState> mPausingTasks = null;
+
+        /**
+         * List of tasks that we are switching to. Upon finish, these will remain visible and
+         * on top.
+         */
+        private ArrayList<TaskState> mOpeningTasks = null;
+
         private WindowContainerToken mPipTask = null;
         private WindowContainerToken mRecentsTask = null;
         private int mRecentsTaskId = 0;
         private TransitionInfo mInfo = null;
-        private ArrayList<SurfaceControl> mOpeningLeashes = null;
         private boolean mOpeningSeparateHome = false;
         private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
         private PictureInPictureSurfaceTransaction mPipTransaction = null;
@@ -120,6 +131,15 @@
         private RemoteAnimationTarget[] mAppearedTargets;
         private boolean mWillFinishToHome = false;
 
+        /** The animation is idle, waiting for the user to choose a task to switch to. */
+        private static final int STATE_NORMAL = 0;
+
+        /** The user chose a new task to switch to and the animation is animating to it. */
+        private static final int STATE_NEW_TASK = 1;
+
+        /** The latest state that the recents animation is operating in. */
+        private int mState = STATE_NORMAL;
+
         void start(RecentsAnimationControllerCompat wrapped, RecentsAnimationListener listener,
                 IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
                 IRemoteTransitionFinishedCallback finishedCallback) {
@@ -132,12 +152,14 @@
             mInfo = info;
             mFinishCB = finishedCallback;
             mPausingTasks = new ArrayList<>();
+            mOpeningTasks = new ArrayList<>();
             mPipTask = null;
             mRecentsTask = null;
             mRecentsTaskId = -1;
             mLeashMap = new ArrayMap<>();
             mTransition = transition;
             mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
+            mState = STATE_NORMAL;
 
             final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
             final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
@@ -178,6 +200,9 @@
                     } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
                         mRecentsTask = taskInfo.token;
                         mRecentsTaskId = taskInfo.taskId;
+                    } else if (change.getMode() == TRANSIT_OPEN
+                            || change.getMode() == TRANSIT_TO_FRONT) {
+                        mOpeningTasks.add(new TaskState(change, target.leash));
                     }
                 }
             }
@@ -189,34 +214,41 @@
 
         @SuppressLint("NewApi")
         boolean merge(TransitionInfo info, SurfaceControl.Transaction t) {
-            SparseArray<TransitionInfo.Change> openingTasks = null;
+            ArrayList<TransitionInfo.Change> openingTasks = null;
+            ArrayList<TransitionInfo.Change> closingTasks = null;
             mAppearedTargets = null;
-            boolean foundHomeOpening = false;
+            mOpeningSeparateHome = false;
+            TransitionInfo.Change recentsOpening = null;
             boolean foundRecentsClosing = false;
             boolean hasChangingApp = false;
-            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final RemoteAnimationTargetCompat.LeafTaskFilter leafTaskFilter =
+                    new RemoteAnimationTargetCompat.LeafTaskFilter();
+            for (int i = 0; i < info.getChanges().size(); ++i) {
                 final TransitionInfo.Change change = info.getChanges().get(i);
+                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+                final boolean isLeafTask = leafTaskFilter.test(change);
                 if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
-                    final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                    if (taskInfo != null) {
+                    if (mRecentsTask.equals(change.getContainer())) {
+                        recentsOpening = change;
+                    } else if (isLeafTask) {
                         if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
                             // This is usually a 3p launcher
-                            foundHomeOpening = true;
+                            mOpeningSeparateHome = true;
                         }
                         if (openingTasks == null) {
-                            openingTasks = new SparseArray<>();
+                            openingTasks = new ArrayList<>();
                         }
-                        if (taskInfo.hasParentTask()) {
-                            // Collects opening leaf tasks only since Launcher monitors leaf task
-                            // ids to perform recents animation.
-                            openingTasks.remove(taskInfo.parentTaskId);
-                        }
-                        openingTasks.put(taskInfo.taskId, change);
+                        openingTasks.add(change);
                     }
                 } else if (change.getMode() == TRANSIT_CLOSE
                         || change.getMode() == TRANSIT_TO_BACK) {
                     if (mRecentsTask.equals(change.getContainer())) {
                         foundRecentsClosing = true;
+                    } else if (isLeafTask) {
+                        if (closingTasks == null) {
+                            closingTasks = new ArrayList<>();
+                        }
+                        closingTasks.add(change);
                     }
                 } else if (change.getMode() == TRANSIT_CHANGE) {
                     hasChangingApp = true;
@@ -234,45 +266,72 @@
                 }
                 return false;
             }
-            if (openingTasks == null) return false;
-            int pauseMatches = 0;
-            if (!foundHomeOpening) {
+            if (recentsOpening != null) {
+                // the recents task re-appeared. This happens if the user gestures before the
+                // task-switch (NEW_TASK) animation finishes.
+                if (mState == STATE_NORMAL) {
+                    Log.e(TAG, "Returning to recents while recents is already idle.");
+                }
+                if (closingTasks == null || closingTasks.size() == 0) {
+                    Log.e(TAG, "Returning to recents without closing any opening tasks.");
+                }
+                // Setup may hide it initially since it doesn't know that overview was still active.
+                t.show(recentsOpening.getLeash());
+                t.setAlpha(recentsOpening.getLeash(), 1.f);
+                mState = STATE_NORMAL;
+            }
+            boolean didMergeThings = false;
+            if (closingTasks != null) {
+                // Cancelling a task-switch. Move the tasks back to mPausing from mOpening
+                for (int i = 0; i < closingTasks.size(); ++i) {
+                    final TransitionInfo.Change change = closingTasks.get(i);
+                    int openingIdx = TaskState.indexOf(mOpeningTasks, change);
+                    if (openingIdx < 0) {
+                        Log.e(TAG, "Back to existing recents animation from an unrecognized "
+                                + "task: " + change.getTaskInfo().taskId);
+                        continue;
+                    }
+                    mPausingTasks.add(mOpeningTasks.remove(openingIdx));
+                    didMergeThings = true;
+                }
+            }
+            if (openingTasks != null && openingTasks.size() > 0) {
+                // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
+                // enter NEW_TASK state since this will start the switch-to animation.
+                final int layer = mInfo.getChanges().size() * 3;
+                final RemoteAnimationTarget[] targets =
+                        new RemoteAnimationTarget[openingTasks.size()];
                 for (int i = 0; i < openingTasks.size(); ++i) {
-                    if (TaskState.indexOf(mPausingTasks, openingTasks.valueAt(i)) >= 0) {
-                        ++pauseMatches;
+                    final TransitionInfo.Change change = openingTasks.get(i);
+                    int pausingIdx = TaskState.indexOf(mPausingTasks, change);
+                    if (pausingIdx >= 0) {
+                        // Something is showing/opening a previously-pausing app.
+                        targets[i] = newTarget(change, layer, mPausingTasks.get(pausingIdx).mLeash);
+                        mOpeningTasks.add(mPausingTasks.remove(pausingIdx));
+                        // Setup hides opening tasks initially, so make it visible again (since we
+                        // are already showing it).
+                        t.show(change.getLeash());
+                        t.setAlpha(change.getLeash(), 1.f);
+                    } else {
+                        // We are receiving new opening tasks, so convert to onTasksAppeared.
+                        targets[i] = newTarget(change, layer, info, t, mLeashMap);
+                        t.reparent(targets[i].leash, mInfo.getRootLeash());
+                        t.setLayer(targets[i].leash, layer);
+                        mOpeningTasks.add(new TaskState(change, targets[i].leash));
                     }
                 }
+                didMergeThings = true;
+                mState = STATE_NEW_TASK;
+                mAppearedTargets = targets;
             }
-            if (pauseMatches > 0) {
-                if (pauseMatches != mPausingTasks.size()) {
-                    // We are not really "returning" properly... something went wrong.
-                    throw new IllegalStateException("\"Concelling\" a recents transitions by "
-                            + "unpausing " + pauseMatches + " apps after pausing "
-                            + mPausingTasks.size() + " apps.");
-                }
-                // In this case, we are "returning" to an already running app, so just consume
-                // the merge and do nothing.
-                info.releaseAllSurfaces();
-                t.close();
-                return true;
-            }
-            final int layer = mInfo.getChanges().size() * 3;
-            mOpeningLeashes = new ArrayList<>();
-            mOpeningSeparateHome = foundHomeOpening;
-            final RemoteAnimationTarget[] targets =
-                    new RemoteAnimationTarget[openingTasks.size()];
-            for (int i = 0; i < openingTasks.size(); ++i) {
-                final TransitionInfo.Change change = openingTasks.valueAt(i);
-                mOpeningLeashes.add(change.getLeash());
-                // We are receiving new opening tasks, so convert to onTasksAppeared.
-                targets[i] = newTarget(change, layer, info, t, mLeashMap);
-                t.reparent(targets[i].leash, mInfo.getRootLeash());
-                t.setLayer(targets[i].leash, layer);
+            if (!didMergeThings) {
+                // Didn't recognize anything in incoming transition so don't merge it.
+                Log.w(TAG, "Don't know how to merge this transition.");
+                return false;
             }
             t.apply();
             // not using the incoming anim-only surfaces
             info.releaseAnimSurfaces();
-            mAppearedTargets = targets;
             return true;
         }
 
@@ -300,6 +359,8 @@
             if (enabled) {
                 // transient launches don't receive focus automatically. Since we are taking over
                 // the gesture now, take focus explicitly.
+                // This also moves recents back to top if the user gestured before a switch
+                // animation finished.
                 try {
                     ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
                 } catch (RemoteException e) {
@@ -336,8 +397,8 @@
                 if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
                 else wct.restoreTransientOrder(mRecentsTask);
             }
-            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mOpeningLeashes == null) {
-                // The gesture went back to opening the app rather than continuing with
+            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+                // The gesture is returning to the pausing-task(s) rather than continuing with
                 // recents, so end the transition by moving the app back to the top (and also
                 // re-showing it's task).
                 for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
@@ -349,25 +410,28 @@
                     wct.restoreTransientOrder(mRecentsTask);
                 }
             } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
-                // Special situaition where 3p launcher was changed during recents (this happens
+                // Special situation where 3p launcher was changed during recents (this happens
                 // during tapltests...). Here we get both "return to home" AND "home opening".
-                // This is basically going home, but we have to restore recents order and also
-                // treat the home "pausing" task properly.
-                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
-                    final TaskState state = mPausingTasks.get(i);
+                // This is basically going home, but we have to restore the recents and home order.
+                for (int i = 0; i < mOpeningTasks.size(); ++i) {
+                    final TaskState state = mOpeningTasks.get(i);
                     if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
-                        // Treat as opening (see above)
+                        // Make sure it is on top.
                         wct.reorder(state.mToken, true /* onTop */);
-                        t.show(state.mTaskSurface);
-                    } else {
-                        // Treat as hiding (see below)
-                        t.hide(state.mTaskSurface);
                     }
+                    t.show(state.mTaskSurface);
+                }
+                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+                    t.hide(mPausingTasks.get(i).mTaskSurface);
                 }
                 if (!mKeyguardLocked && mRecentsTask != null) {
                     wct.restoreTransientOrder(mRecentsTask);
                 }
             } else {
+                // The general case: committing to recents, going home, or switching tasks.
+                for (int i = 0; i < mOpeningTasks.size(); ++i) {
+                    t.show(mOpeningTasks.get(i).mTaskSurface);
+                }
                 for (int i = 0; i < mPausingTasks.size(); ++i) {
                     if (!sendUserLeaveHint) {
                         // This means recents is not *actually* finishing, so of course we gotta
@@ -391,7 +455,7 @@
             try {
                 mFinishCB.onTransitionFinished(wct.isEmpty() ? null : wct, t);
             } catch (RemoteException e) {
-                Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e);
+                Log.e(TAG, "Failed to call animation finish callback", e);
                 t.apply();
             }
             // Only release the non-local created surface references. The animator is responsible
@@ -402,12 +466,13 @@
             mListener = null;
             mFinishCB = null;
             mPausingTasks = null;
+            mOpeningTasks = null;
             mAppearedTargets = null;
             mInfo = null;
-            mOpeningLeashes = null;
             mOpeningSeparateHome = false;
             mLeashMap = null;
             mTransition = null;
+            mState = STATE_NORMAL;
         }
 
         @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index a25b281..c5a06b4 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -24,7 +24,6 @@
 import android.widget.Button;
 
 import com.android.internal.util.EmergencyAffordanceManager;
-import com.android.internal.widget.LockPatternUtils;
 
 /**
  * This class implements a smart emergency button that updates itself based
@@ -40,8 +39,6 @@
     private int mDownY;
     private boolean mLongPressWasDragged;
 
-    private LockPatternUtils mLockPatternUtils;
-
     private final boolean mEnableEmergencyCallWhileSimLocked;
 
     public EmergencyButton(Context context) {
@@ -58,7 +55,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mLockPatternUtils = new LockPatternUtils(mContext);
         if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
             setOnLongClickListener(v -> {
                 if (!mLongPressWasDragged
@@ -95,7 +91,8 @@
         return super.performLongClick();
     }
 
-    void updateEmergencyCallButton(boolean isInCall, boolean hasTelephonyRadio, boolean simLocked) {
+    void updateEmergencyCallButton(boolean isInCall, boolean hasTelephonyRadio, boolean simLocked,
+            boolean isSecure) {
         boolean visible = false;
         if (hasTelephonyRadio) {
             // Emergency calling requires a telephony radio.
@@ -107,7 +104,7 @@
                     visible = mEnableEmergencyCallWhileSimLocked;
                 } else {
                     // Only show if there is a secure screen (pin/pattern/SIM pin/SIM puk);
-                    visible = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
+                    visible = isSecure;
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index ea808eb..f7e8eb4 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
+import android.annotation.SuppressLint;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.content.Intent;
@@ -31,16 +32,22 @@
 import android.util.Log;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.util.EmergencyDialerConstants;
 import com.android.systemui.util.ViewController;
 
+import java.util.concurrent.Executor;
+
 import javax.inject.Inject;
 
 /** View Controller for {@link com.android.keyguard.EmergencyButton}. */
@@ -57,6 +64,9 @@
     private final MetricsLogger mMetricsLogger;
 
     private EmergencyButtonCallback mEmergencyButtonCallback;
+    private LockPatternUtils mLockPatternUtils;
+    private Executor mMainExecutor;
+    private Executor mBackgroundExecutor;
 
     private final KeyguardUpdateMonitorCallback mInfoCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -78,12 +88,15 @@
         }
     };
 
-    private EmergencyButtonController(@Nullable EmergencyButton view,
+    @VisibleForTesting
+    public EmergencyButtonController(@Nullable EmergencyButton view,
             ConfigurationController configurationController,
             KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
             PowerManager powerManager, ActivityTaskManager activityTaskManager,
             ShadeController shadeController,
-            @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+            @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
+            LockPatternUtils lockPatternUtils,
+            Executor mainExecutor, Executor backgroundExecutor) {
         super(view);
         mConfigurationController = configurationController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -93,6 +106,9 @@
         mShadeController = shadeController;
         mTelecomManager = telecomManager;
         mMetricsLogger = metricsLogger;
+        mLockPatternUtils = lockPatternUtils;
+        mMainExecutor = mainExecutor;
+        mBackgroundExecutor = backgroundExecutor;
     }
 
     @Override
@@ -113,13 +129,27 @@
         mConfigurationController.removeCallback(mConfigurationListener);
     }
 
-    private void updateEmergencyCallButton() {
+    /**
+     * Updates the visibility of the emergency button.
+     *
+     * This method runs binder calls in a background thread.
+     */
+    @VisibleForTesting
+    @SuppressLint("MissingPermission")
+    public void updateEmergencyCallButton() {
         if (mView != null) {
-            mView.updateEmergencyCallButton(
-                    mTelecomManager != null && mTelecomManager.isInCall(),
-                    getContext().getPackageManager().hasSystemFeature(
-                            PackageManager.FEATURE_TELEPHONY),
-                    mKeyguardUpdateMonitor.isSimPinVoiceSecure());
+            // Run in bg thread to avoid throttling the main thread with binder call.
+            mBackgroundExecutor.execute(() -> {
+                boolean isInCall = mTelecomManager != null && mTelecomManager.isInCall();
+                boolean isSecure = mLockPatternUtils
+                        .isSecure(KeyguardUpdateMonitor.getCurrentUser());
+                mMainExecutor.execute(() -> mView.updateEmergencyCallButton(
+                        /* isInCall= */ isInCall,
+                        /* hasTelephonyRadio= */ getContext().getPackageManager()
+                                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY),
+                        /* simLocked= */ mKeyguardUpdateMonitor.isSimPinVoiceSecure(),
+                        /* isSecure= */ isSecure));
+            });
         }
     }
 
@@ -129,6 +159,7 @@
     /**
      * Shows the emergency dialer or returns the user to the existing call.
      */
+    @SuppressLint("MissingPermission")
     public void takeEmergencyCallAction() {
         mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL);
         if (mPowerManager != null) {
@@ -136,29 +167,35 @@
         }
         mActivityTaskManager.stopSystemLockTaskMode();
         mShadeController.collapseShade(false);
-        if (mTelecomManager != null && mTelecomManager.isInCall()) {
-            mTelecomManager.showInCallScreen(false);
-            if (mEmergencyButtonCallback != null) {
-                mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
-            }
-        } else {
-            mKeyguardUpdateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
-            if (mTelecomManager == null) {
-                Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
-                return;
-            }
-            Intent emergencyDialIntent =
-                    mTelecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
-                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
-                                    | Intent.FLAG_ACTIVITY_CLEAR_TOP)
-                            .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
-                                    EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
+        // Run in bg thread to avoid throttling the main thread with binder call.
+        mBackgroundExecutor.execute(() -> {
+            boolean isInCall = mTelecomManager != null && mTelecomManager.isInCall();
+            mMainExecutor.execute(() -> {
+                if (isInCall) {
+                    mTelecomManager.showInCallScreen(false);
+                    if (mEmergencyButtonCallback != null) {
+                        mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
+                    }
+                } else {
+                    mKeyguardUpdateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
+                    if (mTelecomManager == null) {
+                        Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+                        return;
+                    }
+                    Intent emergencyDialIntent =
+                            mTelecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
+                                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                                            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+                                            | Intent.FLAG_ACTIVITY_CLEAR_TOP)
+                                    .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
+                                            EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
 
-            getContext().startActivityAsUser(emergencyDialIntent,
-                    ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
-                    new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
-        }
+                    getContext().startActivityAsUser(emergencyDialIntent,
+                            ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
+                            new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
+                }
+            });
+        });
     }
 
     /** */
@@ -178,13 +215,19 @@
         @Nullable
         private final TelecomManager mTelecomManager;
         private final MetricsLogger mMetricsLogger;
+        private final LockPatternUtils mLockPatternUtils;
+        private final Executor mMainExecutor;
+        private final Executor mBackgroundExecutor;
 
         @Inject
         public Factory(ConfigurationController configurationController,
                 KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
                 PowerManager powerManager, ActivityTaskManager activityTaskManager,
                 ShadeController shadeController,
-                @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+                @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
+                LockPatternUtils lockPatternUtils,
+                @Main Executor mainExecutor,
+                @Background Executor backgroundExecutor) {
 
             mConfigurationController = configurationController;
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -194,14 +237,17 @@
             mShadeController = shadeController;
             mTelecomManager = telecomManager;
             mMetricsLogger = metricsLogger;
+            mLockPatternUtils = lockPatternUtils;
+            mMainExecutor = mainExecutor;
+            mBackgroundExecutor = backgroundExecutor;
         }
 
         /** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
         public EmergencyButtonController create(EmergencyButton view) {
             return new EmergencyButtonController(view, mConfigurationController,
                     mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
-                    mShadeController,
-                    mTelecomManager, mMetricsLogger);
+                    mShadeController, mTelecomManager, mMetricsLogger, mLockPatternUtils,
+                    mMainExecutor, mBackgroundExecutor);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 0a4880e..3b0644e 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -33,7 +33,6 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 
@@ -46,7 +45,6 @@
 
     private final TextView mDigitText;
     private final TextView mKlondikeText;
-    private final LockPatternUtils mLockPatternUtils;
     private final PowerManager mPM;
 
     private int mDigit = -1;
@@ -107,7 +105,6 @@
         setOnHoverListener(new LiftToActivateListener(
                 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE)));
 
-        mLockPatternUtils = new LockPatternUtils(context);
         mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 03d999f..ce42534 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -24,6 +24,7 @@
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -195,7 +196,13 @@
         return false;
     }
 
-    void onBatteryLevelChanged(int level, boolean pluggedIn) {
+    /**
+     * Update battery level
+     *
+     * @param level     int between 0 and 100 (representing percentage value)
+     * @param pluggedIn whether the device is plugged in or not
+     */
+    public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) {
         mDrawable.setCharging(pluggedIn);
         mDrawable.setBatteryLevel(level);
         mCharging = pluggedIn;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 3ea3cd1..709ddf5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -231,21 +231,29 @@
             if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
 
     @RawRes
-    private fun getSideFpsAnimationForTransition(rotation: Int): Int = when (rotation) {
-        Surface.ROTATION_90 -> if (isDeviceFolded) {
-            R.raw.biometricprompt_folded_base_topleft
-        } else {
-            R.raw.biometricprompt_portrait_base_topleft
-        }
-        Surface.ROTATION_270 -> if (isDeviceFolded) {
-            R.raw.biometricprompt_folded_base_bottomright
-        } else {
-            R.raw.biometricprompt_portrait_base_bottomright
-        }
-        else -> if (isDeviceFolded) {
-            R.raw.biometricprompt_folded_base_default
-        } else {
-            R.raw.biometricprompt_landscape_base
+    private fun getSideFpsAnimationForTransition(rotation: Int): Int {
+        when (rotation) {
+            Surface.ROTATION_90 -> if (context.isInRearDisplayMode()) {
+                return R.raw.biometricprompt_rear_portrait_reverse_base
+            } else if (isDeviceFolded) {
+                return R.raw.biometricprompt_folded_base_topleft
+            } else {
+                return R.raw.biometricprompt_portrait_base_topleft
+            }
+            Surface.ROTATION_270 -> if (context.isInRearDisplayMode()) {
+                return R.raw.biometricprompt_rear_portrait_base
+            } else if (isDeviceFolded) {
+                return R.raw.biometricprompt_folded_base_bottomright
+            } else {
+                return R.raw.biometricprompt_portrait_base_bottomright
+            }
+            else -> if (context.isInRearDisplayMode()) {
+                return R.raw.biometricprompt_rear_landscape_base
+            } else if (isDeviceFolded) {
+                return R.raw.biometricprompt_folded_base_default
+            } else {
+                return R.raw.biometricprompt_landscape_base
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index e12c170..a7b6e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -658,6 +658,7 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         mIconController.onConfigurationChanged(newConfig);
+        updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 6c49078..ac6a22c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -172,12 +172,16 @@
                 override fun show(
                     sensorId: Int,
                     @BiometricOverlayConstants.ShowReason reason: Int
-                ) =
-                    if (reason.isReasonToAutoShow(activityTaskManager)) {
+                ) {
+                    if (
+                        reason.isReasonToAutoShow(activityTaskManager) &&
+                            !context.isInRearDisplayMode()
+                    ) {
                         show(SideFpsUiRequestSource.AUTO_SHOW, reason)
                     } else {
                         hide(SideFpsUiRequestSource.AUTO_SHOW)
                     }
+                }
 
                 override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
index d0d6f4c..3d56326 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
@@ -36,6 +36,7 @@
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityManager
 import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
 import java.lang.annotation.Retention
 import java.lang.annotation.RetentionPolicy
 
@@ -117,4 +118,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD)
     internal annotation class CredentialType
-}
\ No newline at end of file
+}
+
+fun Context.isInRearDisplayMode(): Boolean = resources.getIntArray(
+        com.android.internal.R.array.config_rearDisplayDeviceStates).isNotEmpty()
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
index 8e062bd..653c12e 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
@@ -46,11 +46,11 @@
     @VisibleForTesting
     protected View mDialogView;
     private MediaOutputDialogFactory mMediaOutputDialogFactory;
-    private String mSwitchBroadcastApp;
+    private String mCurrentBroadcastApp;
     private String mOutputPackageName;
 
     public BroadcastDialog(Context context, MediaOutputDialogFactory mediaOutputDialogFactory,
-            String switchBroadcastApp, String outputPkgName, UiEventLogger uiEventLogger) {
+            String currentBroadcastApp, String outputPkgName, UiEventLogger uiEventLogger) {
         super(context);
         if (DEBUG) {
             Log.d(TAG, "Init BroadcastDialog");
@@ -58,7 +58,7 @@
 
         mContext = getContext();
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
-        mSwitchBroadcastApp = switchBroadcastApp;
+        mCurrentBroadcastApp = currentBroadcastApp;
         mOutputPackageName = outputPkgName;
         mUiEventLogger = uiEventLogger;
     }
@@ -77,20 +77,18 @@
 
         TextView title = mDialogView.requireViewById(R.id.dialog_title);
         TextView subTitle = mDialogView.requireViewById(R.id.dialog_subtitle);
-        title.setText(
-                mContext.getString(R.string.bt_le_audio_broadcast_dialog_title,
-                        MediaDataUtils.getAppLabel(mContext, mOutputPackageName,
-                                mContext.getString(
-                                        R.string.bt_le_audio_broadcast_dialog_unknown_name))));
-        subTitle.setText(
-                mContext.getString(R.string.bt_le_audio_broadcast_dialog_sub_title,
-                        mSwitchBroadcastApp));
+        title.setText(mContext.getString(
+                R.string.bt_le_audio_broadcast_dialog_title, mCurrentBroadcastApp));
+        String switchBroadcastApp = MediaDataUtils.getAppLabel(mContext, mOutputPackageName,
+                mContext.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name));
+        subTitle.setText(mContext.getString(
+                R.string.bt_le_audio_broadcast_dialog_sub_title, switchBroadcastApp));
 
         Button switchBroadcast = mDialogView.requireViewById(R.id.switch_broadcast);
         Button changeOutput = mDialogView.requireViewById(R.id.change_output);
         Button cancelBtn = mDialogView.requireViewById(R.id.cancel);
         switchBroadcast.setText(mContext.getString(
-                R.string.bt_le_audio_broadcast_dialog_switch_app, mSwitchBroadcastApp), null);
+                R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp));
         changeOutput.setOnClickListener((view) -> {
             mMediaOutputDialogFactory.create(mOutputPackageName, true, null);
             dismiss();
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java
index 8a54345..1b699e8 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java
@@ -47,10 +47,15 @@
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
     }
 
-    public void createBroadcastDialog(String switchAppName, String outputPkgName,
+    /** Creates a [BroadcastDialog] for the user to switch broadcast or change the output device
+     *
+     * @param currentBroadcastAppName Indicates the APP name currently broadcasting
+     * @param outputPkgName Indicates the output media package name to be switched
+     */
+    public void createBroadcastDialog(String currentBroadcastAppName, String outputPkgName,
             boolean aboveStatusBar, View view) {
         BroadcastDialog broadcastDialog = new BroadcastDialog(mContext, mMediaOutputDialogFactory,
-                switchAppName, outputPkgName, mUiEventLogger);
+                currentBroadcastAppName, outputPkgName, mUiEventLogger);
         if (view != null) {
             mDialogLaunchAnimator.showFromView(broadcastDialog, view);
         } else {
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
index a2077a3..24f6296 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -108,7 +108,7 @@
 
         mSeekbar.setOnSeekBarChangeListener(mSeekBarListener);
 
-        mIconStart.setOnClickListener((view) -> {
+        mIconStartFrame.setOnClickListener((view) -> {
             final int progress = mSeekbar.getProgress();
             if (progress > 0) {
                 mSeekbar.setProgress(progress - 1);
@@ -116,7 +116,7 @@
             }
         });
 
-        mIconEnd.setOnClickListener((view) -> {
+        mIconEndFrame.setOnClickListener((view) -> {
             final int progress = mSeekbar.getProgress();
             if (progress < mSeekbar.getMax()) {
                 mSeekbar.setProgress(progress + 1);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 19b000b..d5a4146 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -54,6 +54,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.dagger.StartCentralSurfacesModule;
+import com.android.systemui.statusbar.events.StatusBarEventsModule;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.phone.DozeServiceHost;
@@ -104,6 +105,7 @@
         QSModule.class,
         ReferenceScreenshotModule.class,
         RotationLockModule.class,
+        StatusBarEventsModule.class,
         StartCentralSurfacesModule.class,
         VolumeModule.class,
         KeyboardShortcutsModule.class
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f6bb85f..c96946b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -256,6 +256,11 @@
     @BindsOptionalOf
     abstract FingerprintInteractiveToAuthProvider optionalFingerprintInteractiveToAuthProvider();
 
+    @BindsOptionalOf
+    //TODO(b/269430792 remove full qualifier. Full qualifier is used to avoid merge conflict.)
+    abstract com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
+    optionalSystemStatusAnimationScheduler();
+
     @SysUISingleton
     @Binds
     abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
diff --git a/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt b/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt
deleted file mode 100644
index 1390b4d..0000000
--- a/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.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.devicepolicy
-
-import android.app.admin.DevicePolicyManager
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
-import android.content.ComponentName
-
-/** Returns true if the admin of [userId] disallows keyguard shortcuts. */
-fun DevicePolicyManager.areKeyguardShortcutsDisabled(
-    admin: ComponentName? = null,
-    userId: Int
-): Boolean {
-    val flags = getKeyguardDisabledFeatures(admin, userId)
-    return flags and KEYGUARD_DISABLE_SHORTCUTS_ALL == KEYGUARD_DISABLE_SHORTCUTS_ALL ||
-        flags and KEYGUARD_DISABLE_FEATURES_ALL == KEYGUARD_DISABLE_FEATURES_ALL
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index a14aa52..f282aae 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -221,7 +221,14 @@
             mDreamOverlayTouchMonitor.init();
 
             mStateController.setShouldShowComplications(shouldShowComplications());
-            addOverlayWindowLocked(layoutParams);
+
+            // If we are not able to add the overlay window, reset the overlay.
+            if (!addOverlayWindowLocked(layoutParams)) {
+                resetCurrentDreamOverlayLocked();
+                return;
+            }
+
+
             setCurrentStateLocked(Lifecycle.State.RESUMED);
             mStateController.setOverlayActive(true);
             final ComponentName dreamComponent = getDreamComponent();
@@ -267,7 +274,7 @@
      * @param layoutParams The {@link android.view.WindowManager.LayoutParams} which allow inserting
      *                     into the dream window.
      */
-    private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
+    private boolean addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
         mWindow = new PhoneWindow(mContext);
         // Default to SystemUI name for TalkBack.
         mWindow.setTitle("");
@@ -292,9 +299,22 @@
         // risk an IllegalStateException in some cases when setting the container view as the
         // window's content view and the container view hasn't been properly removed previously).
         removeContainerViewFromParentLocked();
+
         mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
 
-        mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
+        // It is possible that a dream's window (and the dream as a whole) is no longer valid by
+        // the time the overlay service processes the dream. This can happen for example if
+        // another dream is started immediately after the existing dream begins. In this case, the
+        // overlay service should identify the situation through the thrown exception and tear down
+        // the overlay.
+        try {
+            mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
+            return true;
+        } catch (WindowManager.BadTokenException exception) {
+            Log.e(TAG, "Dream activity window invalid: " + layoutParams.packageName,
+                    exception);
+            return false;
+        }
     }
 
     private void removeContainerViewFromParentLocked() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
index f5bbba7..776b7bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
@@ -34,7 +34,7 @@
 
     @Override
     public void show() {
-        mStatusBarKeyguardViewManager.showBouncer(false);
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 0b41c55..ab78b1b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -155,7 +155,7 @@
     // TODO(b/255618149): Tracking Bug
     @JvmField
     val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
-        unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = true)
+        releasedFlag(216, "customizable_lock_screen_quick_affordances")
 
     /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
     // TODO(b/256513609): Tracking Bug
@@ -187,7 +187,7 @@
 
     // TODO(b/262780002): Tracking Bug
     @JvmField
-    val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = true)
+    val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui")
 
     /** A different path for unocclusion transitions back to keyguard */
     // TODO(b/262859270): Tracking Bug
@@ -214,10 +214,9 @@
     // TODO(b/266242192): Tracking Bug
     @JvmField
     val LOCK_SCREEN_LONG_PRESS_ENABLED =
-        unreleasedFlag(
+        releasedFlag(
             228,
-            "lock_screen_long_press_enabled",
-            teamfood = true,
+            "lock_screen_long_press_enabled"
         )
 
     // 300 - power menu
@@ -247,6 +246,9 @@
             "qs_user_detail_shortcut"
         )
 
+    @JvmField
+    val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = false)
+
     // TODO(b/254512383): Tracking Bug
     @JvmField
     val FULL_SCREEN_USER_SWITCHER =
@@ -295,6 +297,9 @@
     val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
         unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
 
+    // TODO(b/265892345): Tracking Bug
+    val PLUG_IN_STATUS_BAR_CHIP = unreleasedFlag(265892345, "plug_in_status_bar_chip")
+
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
     val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index dfe1038..0140529 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -33,6 +34,7 @@
 class AlternateBouncerInteractor
 @Inject
 constructor(
+    private val keyguardStateController: KeyguardStateController,
     private val bouncerRepository: KeyguardBouncerRepository,
     private val biometricSettingsRepository: BiometricSettingsRepository,
     private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
@@ -102,7 +104,8 @@
                 biometricSettingsRepository.isFingerprintEnrolled.value &&
                 biometricSettingsRepository.isStrongBiometricAllowed.value &&
                 biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
-                !deviceEntryFingerprintAuthRepository.isLockedOut.value
+                !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
+                !keyguardStateController.isUnlocked
         } else {
             legacyAlternateBouncer != null &&
                 keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 9b5f7f6..dfbe1c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
@@ -411,10 +410,16 @@
         )
     }
 
-    private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
-        withContext(backgroundDispatcher) {
-            devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
-        }
+    private suspend fun isFeatureDisabledByDevicePolicy(): Boolean {
+        val flags =
+            withContext(backgroundDispatcher) {
+                devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)
+            }
+        val flagsToCheck =
+            DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL or
+                DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
+        return flagsToCheck and flags != 0
+    }
 
     companion object {
         private const val TAG = "KeyguardQuickAffordanceInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 68d2c5c..6cf051a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.R
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -68,6 +69,7 @@
 import com.android.systemui.util.traceSection
 import java.io.PrintWriter
 import java.util.TreeMap
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
@@ -93,7 +95,8 @@
     private val mediaHostStatesManager: MediaHostStatesManager,
     private val activityStarter: ActivityStarter,
     private val systemClock: SystemClock,
-    @Main executor: DelayableExecutor,
+    @Main private val mainExecutor: DelayableExecutor,
+    @Background private val backgroundExecutor: Executor,
     private val mediaManager: MediaDataManager,
     configurationController: ConfigurationController,
     falsingCollector: FalsingCollector,
@@ -256,7 +259,7 @@
             MediaCarouselScrollHandler(
                 mediaCarousel,
                 pageIndicator,
-                executor,
+                mainExecutor,
                 this::onSwipeToDismiss,
                 this::updatePageIndicatorLocation,
                 this::updateSeekbarListening,
@@ -618,10 +621,50 @@
                 MediaPlayerData.visiblePlayerKeys()
                     .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
             if (existingPlayer == null) {
-                val newPlayer = mediaControlPanelFactory.get()
-                newPlayer.attachPlayer(
-                    MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
+                setupNewPlayer(key, data, isSsReactivated, curVisibleMediaKey)
+            } else {
+                existingPlayer.bindPlayer(data, key)
+                MediaPlayerData.addMediaPlayer(
+                    key,
+                    data,
+                    existingPlayer,
+                    systemClock,
+                    isSsReactivated,
+                    debugLogger
                 )
+                val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
+                // In case of recommendations hits.
+                // Check the playing status of media player and the package name.
+                // To make sure we scroll to the right app's media player.
+                if (
+                    isReorderingAllowed ||
+                        shouldScrollToKey &&
+                            data.isPlaying == true &&
+                            packageName == data.packageName
+                ) {
+                    reorderAllPlayers(curVisibleMediaKey, key)
+                } else {
+                    needsReordering = true
+                }
+                updatePageIndicator()
+                mediaCarouselScrollHandler.onPlayersChanged()
+                mediaFrame.requiresRemeasuring = true
+            }
+            return existingPlayer == null
+        }
+
+    private fun setupNewPlayer(
+        key: String,
+        data: MediaData,
+        isSsReactivated: Boolean,
+        curVisibleMediaKey: MediaPlayerData.MediaSortKey?,
+    ) {
+        backgroundExecutor.execute {
+            val mediaViewHolder = createMediaViewHolderInBg()
+            // Add the new player in the main thread.
+            mainExecutor.execute {
+                val newPlayer = mediaControlPanelFactory.get()
+                newPlayer.attachPlayer(mediaViewHolder)
                 newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
                 val lp =
                     LinearLayout.LayoutParams(
@@ -651,36 +694,16 @@
                 } else {
                     needsReordering = true
                 }
-            } else {
-                existingPlayer.bindPlayer(data, key)
-                MediaPlayerData.addMediaPlayer(
-                    key,
-                    data,
-                    existingPlayer,
-                    systemClock,
-                    isSsReactivated,
-                    debugLogger
-                )
-                val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
-                // In case of recommendations hits.
-                // Check the playing status of media player and the package name.
-                // To make sure we scroll to the right app's media player.
-                if (
-                    isReorderingAllowed ||
-                        shouldScrollToKey &&
-                            data.isPlaying == true &&
-                            packageName == data.packageName
-                ) {
-                    reorderAllPlayers(curVisibleMediaKey, key)
-                } else {
-                    needsReordering = true
-                }
+                updatePageIndicator()
+                mediaCarouselScrollHandler.onPlayersChanged()
+                mediaFrame.requiresRemeasuring = true
             }
-            updatePageIndicator()
-            mediaCarouselScrollHandler.onPlayersChanged()
-            mediaFrame.requiresRemeasuring = true
-            return existingPlayer == null
         }
+    }
+
+    private fun createMediaViewHolderInBg(): MediaViewHolder {
+        return MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
+    }
 
     private fun addSmartspaceMediaRecommendations(
         key: String,
@@ -714,15 +737,14 @@
                     debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
                 }
             }
-
             val newRecs = mediaControlPanelFactory.get()
-            newRecs.attachRecommendation(
+            val recommendationViewHolder =
                 RecommendationViewHolder.create(
                     LayoutInflater.from(context),
                     mediaContent,
                     mediaFlags.isRecommendationCardUpdateEnabled()
                 )
-            )
+            newRecs.attachRecommendation(recommendationViewHolder)
             newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
             val lp =
                 LinearLayout.LayoutParams(
@@ -746,17 +768,6 @@
             reorderAllPlayers(curVisibleMediaKey)
             updatePageIndicator()
             mediaFrame.requiresRemeasuring = true
-            // Check postcondition: mediaContent should have the same number of children as there
-            // are
-            // elements in mediaPlayers.
-            if (MediaPlayerData.players().size != mediaContent.childCount) {
-                Log.e(
-                    TAG,
-                    "Size of players list and number of views in carousel are out of sync. " +
-                        "Players size is ${MediaPlayerData.players().size}. " +
-                        "View count is ${mediaContent.childCount}."
-                )
-            }
         }
 
     fun removePlayer(
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 288266a..097cc3e 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
@@ -227,7 +227,7 @@
     private final BroadcastDialogController mBroadcastDialogController;
     private boolean mIsCurrentBroadcastedApp = false;
     private boolean mShowBroadcastDialogButton = false;
-    private String mSwitchBroadcastApp;
+    private String mCurrentBroadcastApp;
     private MultiRippleController mMultiRippleController;
     private TurbulenceNoiseController mTurbulenceNoiseController;
     private final FeatureFlags mFeatureFlags;
@@ -573,9 +573,8 @@
             // TODO(b/233698402): Use the package name instead of app label to avoid the
             // unexpected result.
             mIsCurrentBroadcastedApp = device != null
-                    && TextUtils.equals(device.getName(),
-                    MediaDataUtils.getAppLabel(mContext, mPackageName, mContext.getString(
-                            R.string.bt_le_audio_broadcast_dialog_unknown_name)));
+                && TextUtils.equals(device.getName(),
+                    mContext.getString(R.string.broadcasting_description_is_broadcasting));
             useDisabledAlpha = !mIsCurrentBroadcastedApp;
             // Always be enabled if the broadcast button is shown
             isTapEnabled = true;
@@ -630,8 +629,8 @@
                         // media output dialog.
                         if (!mIsCurrentBroadcastedApp) {
                             mLogger.logOpenBroadcastDialog(mUid, mPackageName, mInstanceId);
-                            mSwitchBroadcastApp = device.getName().toString();
-                            mBroadcastDialogController.createBroadcastDialog(mSwitchBroadcastApp,
+                            mCurrentBroadcastApp = device.getName().toString();
+                            mBroadcastDialogController.createBroadcastDialog(mCurrentBroadcastApp,
                                     mPackageName, true, mMediaViewHolder.getSeamlessButton());
                         } else {
                             mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index f1a5c3e..27e99f7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -436,6 +436,19 @@
         }
     }
 
+    @Override
+    public void toggleTaskbar() {
+        if (mOverviewProxyService.getProxy() == null) {
+            return;
+        }
+
+        try {
+            mOverviewProxyService.getProxy().onTaskbarToggled();
+        } catch (RemoteException e) {
+            Log.e(TAG, "onTaskbarToggled() failed", e);
+        }
+    }
+
     private void clearTransient() {
         if (mTaskbarTransientShowing) {
             mTaskbarTransientShowing = false;
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 5702681..be615d6 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,21 +17,17 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
-import android.app.admin.DevicePolicyManager
 import android.content.ActivityNotFoundException
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
-import android.os.Build
 import android.os.UserManager
 import android.util.Log
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -53,10 +49,8 @@
     private val optionalBubbles: Optional<Bubbles>,
     private val optionalKeyguardManager: Optional<KeyguardManager>,
     private val optionalUserManager: Optional<UserManager>,
-    private val devicePolicyManager: DevicePolicyManager,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
     private val uiEventLogger: UiEventLogger,
-    private val userTracker: UserTracker,
 ) {
 
     /**
@@ -86,18 +80,6 @@
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
 
-        val isKeyguardLocked = keyguardManager.isKeyguardLocked
-        // KeyguardQuickAffordanceInteractor blocks the quick affordance from showing in the
-        // keyguard if it is not allowed by the admin policy. Here we block any other way to show
-        // note task when the screen is locked.
-        if (
-            isKeyguardLocked &&
-                devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
-        ) {
-            logDebug { "Enterprise policy disallows launching note app when the screen is locked." }
-            return
-        }
-
         val noteTaskInfo = resolver.resolveInfo() ?: return
 
         uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) }
@@ -105,7 +87,7 @@
         // TODO(b/266686199): We should handle when app not available. For now, we log.
         val intent = noteTaskInfo.toCreateNoteIntent()
         try {
-            if (isInMultiWindowMode || isKeyguardLocked) {
+            if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
                 context.startActivity(intent)
             } else {
                 bubbles.showOrHideAppBubble(intent)
@@ -162,7 +144,7 @@
     }
 
     companion object {
-        val TAG = NoteTaskController::class.simpleName.orEmpty()
+        private val TAG = NoteTaskController::class.simpleName.orEmpty()
 
         private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent {
             return Intent(ACTION_CREATE_NOTE)
@@ -183,9 +165,3 @@
         const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
     }
 }
-
-private inline fun logDebug(message: () -> String) {
-    if (Build.IS_DEBUGGABLE) {
-        Log.d(NoteTaskController.TAG, message())
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 8ad2f86..79167f2 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -16,7 +16,11 @@
 
 import android.content.Context
 import android.util.AttributeSet
+import android.view.Gravity.CENTER_VERTICAL
+import android.view.Gravity.END
 import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.ImageView
 import android.widget.LinearLayout
 import com.android.settingslib.Utils
@@ -35,7 +39,7 @@
     private var iconSize = 0
     private var iconColor = 0
 
-    private lateinit var iconsContainer: LinearLayout
+    private val iconsContainer: LinearLayout
 
     var privacyList = emptyList<PrivacyItem>()
         set(value) {
@@ -43,11 +47,13 @@
             updateView(PrivacyChipBuilder(context, field))
         }
 
-    override fun onFinishInflate() {
-        super.onFinishInflate()
-
+    init {
+        inflate(context, R.layout.ongoing_privacy_chip, this)
+        id = R.id.privacy_chip
+        layoutParams = LayoutParams(WRAP_CONTENT, MATCH_PARENT, CENTER_VERTICAL or END)
+        clipChildren = true
+        clipToPadding = true
         iconsContainer = requireViewById(R.id.icons_container)
-
         updateResources()
     }
 
@@ -107,6 +113,6 @@
         val padding = context.resources
                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
         iconsContainer.setPaddingRelative(padding, 0, padding, 0)
-        iconsContainer.background = context.getDrawable(R.drawable.privacy_chip_bg)
+        iconsContainer.background = context.getDrawable(R.drawable.statusbar_privacy_chip_bg)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
index be93550..c70cce9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -183,7 +183,7 @@
     }
 
     fun getRestoredTilePosition(tile: String): Int =
-        restoredTiles?.get(tile)?.index ?: QSTileHost.POSITION_AT_END
+        restoredTiles?.get(tile)?.index ?: QSHost.POSITION_AT_END
 
     /**
      * Returns `true` if the tile has been auto-added before
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 979884c..a7aac5a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -135,7 +135,7 @@
     private int mNumQuickTiles;
     private int mLastQQSTileHeight;
     private float mLastPosition;
-    private final QSTileHost mHost;
+    private final QSHost mHost;
     private final Executor mExecutor;
     private boolean mShowCollapsedOnKeyguard;
     private int mQQSTop;
@@ -146,7 +146,7 @@
     @Inject
     public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
             QSPanelController qsPanelController,
-            QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
+            QuickQSPanelController quickQSPanelController, QSHost qsTileHost,
             @Main Executor executor, TunerService tunerService,
             QSExpansionPathInterpolator qsExpansionPathInterpolator) {
         mQs = qs;
@@ -485,7 +485,7 @@
         if (specs.isEmpty()) {
             // specs should not be empty in a valid secondary page, as we scrolled to it.
             // We may crash later on because there's a null animator.
-            specs = mQsPanelController.getHost().mTileSpecs;
+            specs = mHost.getSpecs();
             Log.e(TAG, "Trying to create animators for empty page " + page + ". Tiles: " + specs);
             // return null;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 1da30ad..a71e6dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -14,15 +14,48 @@
 
 package com.android.systemui.qs;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.res.Resources;
+import android.os.Build;
+import android.provider.Settings;
 
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.plugins.qs.QSTileView;
+import com.android.systemui.util.leak.GarbageMonitor;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 public interface QSHost {
+    String TILES_SETTING = Settings.Secure.QS_TILES;
+    int POSITION_AT_END = -1;
+
+    /**
+     * Returns the default QS tiles for the context.
+     * @param context the context to obtain the resources from
+     * @return a list of specs of the default tiles
+     */
+    static List<String> getDefaultSpecs(Context context) {
+        final ArrayList<String> tiles = new ArrayList();
+
+        final Resources res = context.getResources();
+        final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
+
+        tiles.addAll(Arrays.asList(defaultTileList.split(",")));
+        if (Build.IS_DEBUGGABLE
+                && GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
+            tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
+        }
+        return tiles;
+    }
+
     void warn(String message, Throwable t);
     void collapsePanels();
     void forceCollapsePanels();
@@ -37,6 +70,44 @@
     void removeTile(String tileSpec);
     void removeTiles(Collection<String> specs);
 
+    List<String> getSpecs();
+    /**
+     * Create a view for a tile, iterating over all possible {@link QSFactory}.
+     *
+     * @see QSFactory#createTileView
+     */
+    QSTileView createTileView(Context themedContext, QSTile tile, boolean collapsedView);
+    /** Create a {@link QSTile} of a {@code tileSpec} type. */
+    QSTile createTile(String tileSpec);
+
+    /**
+     * Add a tile to the end
+     *
+     * @param spec string matching a pre-defined tilespec
+     */
+    void addTile(String spec);
+
+    /**
+     * Add a tile into the requested spot, or at the end if the position is greater than the number
+     * of tiles.
+     * @param spec string matching a pre-defined tilespec
+     * @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
+     */
+    void addTile(String spec, int requestPosition);
+    void addTile(ComponentName tile);
+
+    /**
+     * Adds a custom tile to the set of current tiles.
+     * @param tile the component name of the {@link android.service.quicksettings.TileService}
+     * @param end if true, the tile will be added at the end. If false, at the beginning.
+     */
+    void addTile(ComponentName tile, boolean end);
+    void removeTileByUser(ComponentName tile);
+    void changeTilesByUser(List<String> previousTiles, List<String> newTiles);
+
+    boolean isTileAdded(ComponentName componentName, int userId);
+    void setTileAdded(ComponentName componentName, int userId, boolean added);
+
     int indexOf(String tileSpec);
 
     InstanceId getNewInstanceId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index a4f0bdf..b476521 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -79,7 +79,6 @@
     protected boolean mExpanded;
     protected boolean mListening;
 
-    @Nullable protected QSTileHost mHost;
     private final List<OnConfigurationChangedListener> mOnConfigurationChangedListeners =
             new ArrayList<>();
 
@@ -359,11 +358,6 @@
         }
     }
 
-    @Nullable
-    public QSTileHost getHost() {
-        return mHost;
-    }
-
     public void updateResources() {
         updatePadding();
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index b36d0fa..83b373d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -71,7 +71,7 @@
 
     @Inject
     QSPanelController(QSPanel view, TunerService tunerService,
-            QSTileHost qstileHost, QSCustomizerController qsCustomizerController,
+            QSHost qsHost, QSCustomizerController qsCustomizerController,
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QS_PANEL) MediaHost mediaHost,
             QSTileRevealController.Factory qsTileRevealControllerFactory,
@@ -80,7 +80,7 @@
             BrightnessSliderController.Factory brightnessSliderFactory,
             FalsingManager falsingManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
-        super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
+        super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager);
         mTunerService = tunerService;
         mQsCustomizerController = qsCustomizerController;
@@ -172,12 +172,6 @@
         mBrightnessMirrorHandler.setController(brightnessMirrorController);
     }
 
-    /** Get the QSTileHost this panel uses. */
-    public QSTileHost getHost() {
-        return mHost;
-    }
-
-
     /** Update appearance of QSPanel. */
     public void updateResources() {
         mView.updateResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index bbdf6cc..2668d2e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -64,7 +64,7 @@
 public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewController<T>
         implements Dumpable{
     private static final String TAG = "QSPanelControllerBase";
-    protected final QSTileHost mHost;
+    protected final QSHost mHost;
     private final QSCustomizerController mQsCustomizerController;
     private final boolean mUsingMediaPlayer;
     protected final MediaHost mMediaHost;
@@ -128,7 +128,7 @@
 
     protected QSPanelControllerBase(
             T view,
-            QSTileHost host,
+            QSHost host,
             QSCustomizerController qsCustomizerController,
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             MediaHost mediaHost,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 98af9df..0ead979 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -18,7 +18,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.os.Build;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings.Secure;
@@ -56,17 +55,14 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.jetbrains.annotations.NotNull;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -94,16 +90,13 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final int MAX_QS_INSTANCE_ID = 1 << 20;
 
-    public static final int POSITION_AT_END = -1;
-    public static final String TILES_SETTING = Secure.QS_TILES;
-
     // Shared prefs that hold tile lifecycle info.
     @VisibleForTesting
     static final String TILES = "tiles_prefs";
 
     private final Context mContext;
     private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();
-    protected final ArrayList<String> mTileSpecs = new ArrayList<>();
+    private final ArrayList<String> mTileSpecs = new ArrayList<>();
     private final TunerService mTunerService;
     private final PluginManager mPluginManager;
     private final DumpManager mDumpManager;
@@ -117,7 +110,6 @@
     private final List<Callback> mCallbacks = new ArrayList<>();
     @Nullable
     private AutoTileManager mAutoTiles;
-    private final StatusBarIconController mIconController;
     private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
     private int mCurrentUser;
     private final Optional<CentralSurfaces> mCentralSurfacesOptional;
@@ -135,7 +127,6 @@
 
     @Inject
     public QSTileHost(Context context,
-            StatusBarIconController iconController,
             QSFactory defaultFactory,
             @Main Executor mainExecutor,
             PluginManager pluginManager,
@@ -152,7 +143,6 @@
             TileLifecycleManager.Factory tileLifecycleManagerFactory,
             UserFileManager userFileManager
     ) {
-        mIconController = iconController;
         mContext = context;
         mUserContext = context;
         mTunerService = tunerService;
@@ -186,10 +176,6 @@
         });
     }
 
-    public StatusBarIconController getIconController() {
-        return mIconController;
-    }
-
     @Override
     public InstanceId getNewInstanceId() {
         return mInstanceIdSequence.newInstanceId();
@@ -438,12 +424,7 @@
         addTile(spec, POSITION_AT_END);
     }
 
-    /**
-     * Add a tile into the requested spot, or at the end if the position is greater than the number
-     * of tiles.
-     * @param spec string matching a pre-defined tilespec
-     * @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
-     */
+    @Override
     public void addTile(String spec, int requestPosition) {
         mMainExecutor.execute(() ->
                 changeTileSpecs(tileSpecs -> {
@@ -483,15 +464,12 @@
         }
     }
 
+    @Override
     public void addTile(ComponentName tile) {
         addTile(tile, /* end */ false);
     }
 
-    /**
-     * Adds a custom tile to the set of current tiles.
-     * @param tile the component name of the {@link android.service.quicksettings.TileService}
-     * @param end if true, the tile will be added at the end. If false, at the beginning.
-     */
+    @Override
     public void addTile(ComponentName tile, boolean end) {
         String spec = CustomTile.toSpec(tile);
         addTile(spec, end ? POSITION_AT_END : 0);
@@ -501,6 +479,7 @@
      * This will call through {@link #changeTilesByUser}. It should only be used when a tile is
      * removed by a <b>user action</b> like {@code adb}.
      */
+    @Override
     public void removeTileByUser(ComponentName tile) {
         mMainExecutor.execute(() -> {
             List<String> newSpecs = new ArrayList<>(mTileSpecs);
@@ -519,6 +498,7 @@
      * that are removed.
      */
     @MainThread
+    @Override
     public void changeTilesByUser(List<String> previousTiles, List<String> newTiles) {
         final List<String> copy = new ArrayList<>(previousTiles);
         final int NP = copy.size();
@@ -542,8 +522,8 @@
         saveTilesToSettings(newTiles);
     }
 
-    /** Create a {@link QSTile} of a {@code tileSpec} type. */
     @Nullable
+    @Override
     public QSTile createTile(String tileSpec) {
         for (int i = 0; i < mQsFactories.size(); i++) {
             QSTile t = mQsFactories.get(i).createTile(tileSpec);
@@ -554,11 +534,7 @@
         return null;
     }
 
-    /**
-     * Create a view for a tile, iterating over all possible {@link QSFactory}.
-     *
-     * @see QSFactory#createTileView
-     */
+    @Override
     public QSTileView createTileView(Context themedContext, QSTile tile, boolean collapsedView) {
         for (int i = 0; i < mQsFactories.size(); i++) {
             QSTileView view = mQsFactories.get(i)
@@ -578,6 +554,7 @@
      *                      tile.
      * @param userId the user to check
      */
+    @Override
     public boolean isTileAdded(ComponentName componentName, int userId) {
         return mUserFileManager
                 .getSharedPreferences(TILES, 0, userId)
@@ -593,6 +570,7 @@
      * @param userId the user for this tile
      * @param added {@code true} if the tile is being added, {@code false} otherwise
      */
+    @Override
     public void setTileAdded(ComponentName componentName, int userId, boolean added) {
         mUserFileManager.getSharedPreferences(TILES, 0, userId)
                 .edit()
@@ -600,6 +578,11 @@
                 .apply();
     }
 
+    @Override
+    public List<String> getSpecs() {
+        return mTileSpecs;
+    }
+
     protected static List<String> loadTileSpecs(Context context, String tileList) {
         final Resources res = context.getResources();
 
@@ -617,7 +600,7 @@
             if (tile.isEmpty()) continue;
             if (tile.equals("default")) {
                 if (!addedDefault) {
-                    List<String> defaultSpecs = getDefaultSpecs(context);
+                    List<String> defaultSpecs = QSHost.getDefaultSpecs(context);
                     for (String spec : defaultSpecs) {
                         if (!addedSpecs.contains(spec)) {
                             tiles.add(spec);
@@ -650,25 +633,6 @@
         return tiles;
     }
 
-    /**
-     * Returns the default QS tiles for the context.
-     * @param context the context to obtain the resources from
-     * @return a list of specs of the default tiles
-     */
-    public static List<String> getDefaultSpecs(Context context) {
-        final ArrayList<String> tiles = new ArrayList<String>();
-
-        final Resources res = context.getResources();
-        final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
-
-        tiles.addAll(Arrays.asList(defaultTileList.split(",")));
-        if (Build.IS_DEBUGGABLE
-                && GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
-            tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
-        }
-        return tiles;
-    }
-
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("QSTileHost:");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 6aabe3b..2d54313 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -48,7 +48,7 @@
     private final Provider<Boolean> mUsingCollapsedLandscapeMediaProvider;
 
     @Inject
-    QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
+    QuickQSPanelController(QuickQSPanel view, QSHost qsHost,
             QSCustomizerController qsCustomizerController,
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QUICK_QS_PANEL) MediaHost mediaHost,
@@ -57,7 +57,7 @@
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
             DumpManager dumpManager
     ) {
-        super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
+        super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
                 uiEventLogger, qsLogger, dumpManager);
         mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index 9739011..a319fb8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -40,7 +40,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSEditEvent;
 import com.android.systemui.qs.QSFragment;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -57,7 +57,7 @@
 @QSScope
 public class QSCustomizerController extends ViewController<QSCustomizer> {
     private final TileQueryHelper mTileQueryHelper;
-    private final QSTileHost mQsTileHost;
+    private final QSHost mQsHost;
     private final TileAdapter mTileAdapter;
     private final ScreenLifecycle mScreenLifecycle;
     private final KeyguardStateController mKeyguardStateController;
@@ -104,12 +104,12 @@
 
     @Inject
     protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
-            QSTileHost qsTileHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
+            QSHost qsHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
             KeyguardStateController keyguardStateController, LightBarController lightBarController,
             ConfigurationController configurationController, UiEventLogger uiEventLogger) {
         super(view);
         mTileQueryHelper = tileQueryHelper;
-        mQsTileHost = qsTileHost;
+        mQsHost = qsHost;
         mTileAdapter = tileAdapter;
         mScreenLifecycle = screenLifecycle;
         mKeyguardStateController = keyguardStateController;
@@ -175,7 +175,7 @@
 
 
     private void reset() {
-        mTileAdapter.resetTileSpecs(QSTileHost.getDefaultSpecs(getContext()));
+        mTileAdapter.resetTileSpecs(QSHost.getDefaultSpecs(getContext()));
     }
 
     public boolean isCustomizing() {
@@ -192,7 +192,7 @@
                 mView.show(x, y, mTileAdapter);
                 mUiEventLogger.log(QSEditEvent.QS_EDIT_OPEN);
             }
-            mTileQueryHelper.queryTiles(mQsTileHost);
+            mTileQueryHelper.queryTiles(mQsHost);
             mKeyguardStateController.addCallback(mKeyguardCallback);
             mView.updateNavColors(mLightBarController);
         }
@@ -258,13 +258,13 @@
 
     private void save() {
         if (mTileQueryHelper.isFinished()) {
-            mTileAdapter.saveSpecs(mQsTileHost);
+            mTileAdapter.saveSpecs(mQsHost);
         }
     }
 
     private void setTileSpecs() {
         List<String> specs = new ArrayList<>();
-        for (QSTile tile : mQsTileHost.getTiles()) {
+        for (QSTile tile : mQsHost.getTiles()) {
             specs.add(tile.getTileSpec());
         }
         mTileAdapter.setTileSpecs(specs);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index d84b12c..6a05684 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -45,7 +45,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSEditEvent;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.customize.TileAdapter.Holder;
 import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
 import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
@@ -91,7 +91,7 @@
     private ItemDecoration mDecoration;
     private final MarginTileDecoration mMarginDecoration;
     private final int mMinNumTiles;
-    private final QSTileHost mHost;
+    private final QSHost mHost;
     private int mEditIndex;
     private int mTileDividerIndex;
     private int mFocusIndex;
@@ -117,7 +117,7 @@
     @Inject
     public TileAdapter(
             @QSThemedContext Context context,
-            QSTileHost qsHost,
+            QSHost qsHost,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mHost = qsHost;
@@ -176,7 +176,7 @@
         mMarginDecoration.setHalfMargin(halfMargin);
     }
 
-    public void saveSpecs(QSTileHost host) {
+    public void saveSpecs(QSHost host) {
         List<String> newSpecs = new ArrayList<>();
         clearAccessibilityState();
         for (int i = 1; i < mTiles.size() && mTiles.get(i) != null; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 32a7916..d9f4484 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -38,7 +38,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
@@ -85,7 +85,7 @@
         mListener = listener;
     }
 
-    public void queryTiles(QSTileHost host) {
+    public void queryTiles(QSHost host) {
         mTiles.clear();
         mSpecs.clear();
         mFinished = false;
@@ -97,7 +97,7 @@
         return mFinished;
     }
 
-    private void addCurrentAndStockTiles(QSTileHost host) {
+    private void addCurrentAndStockTiles(QSHost host) {
         String stock = mContext.getString(R.string.quick_settings_tiles_stock);
         String current = Settings.Secure.getString(mContext.getContentResolver(),
                 Settings.Secure.QS_TILES);
@@ -153,14 +153,14 @@
     private class TileCollector implements QSTile.Callback {
 
         private final List<TilePair> mQSTileList = new ArrayList<>();
-        private final QSTileHost mQSTileHost;
+        private final QSHost mQSHost;
 
-        TileCollector(List<QSTile> tilesToAdd, QSTileHost host) {
+        TileCollector(List<QSTile> tilesToAdd, QSHost host) {
             for (QSTile tile: tilesToAdd) {
                 TilePair pair = new TilePair(tile);
                 mQSTileList.add(pair);
             }
-            mQSTileHost = host;
+            mQSHost = host;
             if (tilesToAdd.isEmpty()) {
                 mBgExecutor.execute(this::finished);
             }
@@ -168,7 +168,7 @@
 
         private void finished() {
             notifyTilesChanged(false);
-            addPackageTiles(mQSTileHost);
+            addPackageTiles(mQSHost);
         }
 
         private void startListening() {
@@ -207,7 +207,7 @@
         }
     }
 
-    private void addPackageTiles(final QSTileHost host) {
+    private void addPackageTiles(final QSHost host) {
         mBgExecutor.execute(() -> {
             Collection<QSTile> params = host.getTiles();
             PackageManager pm = mContext.getPackageManager();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 27ae171..431d6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -67,7 +67,7 @@
     static AutoTileManager provideAutoTileManager(
             Context context,
             AutoAddTracker.Builder autoAddTrackerBuilder,
-            QSTileHost host,
+            QSHost host,
             @Background Handler handler,
             SecureSettings secureSettings,
             HotspotController hotspotController,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 237b66e..d9e5580 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -25,13 +25,13 @@
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.internal.statusbar.IAddTileResultCallback
+import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
+import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.R
-import com.android.systemui.statusbar.CommandQueue
 import java.io.PrintWriter
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.function.Consumer
@@ -40,14 +40,14 @@
 private const val TAG = "TileServiceRequestController"
 
 /**
- * Controller to interface between [TileRequestDialog] and [QSTileHost].
+ * Controller to interface between [TileRequestDialog] and [QSHost].
  */
 class TileServiceRequestController constructor(
-    private val qsTileHost: QSTileHost,
+    private val qsHost: QSHost,
     private val commandQueue: CommandQueue,
     private val commandRegistry: CommandRegistry,
     private val eventLogger: TileRequestDialogEventLogger,
-    private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsTileHost.context) }
+    private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) }
 ) {
 
     companion object {
@@ -93,7 +93,7 @@
     }
 
     private fun addTile(componentName: ComponentName) {
-        qsTileHost.addTile(componentName, true)
+        qsHost.addTile(componentName, true)
     }
 
     @VisibleForTesting
@@ -158,7 +158,7 @@
 
     private fun isTileAlreadyAdded(componentName: ComponentName): Boolean {
         val spec = CustomTile.toSpec(componentName)
-        return qsTileHost.indexOf(spec) != -1
+        return qsHost.indexOf(spec) != -1
     }
 
     inner class TileServiceRequestCommand : Command {
@@ -194,13 +194,13 @@
         private val commandQueue: CommandQueue,
         private val commandRegistry: CommandRegistry
     ) {
-        fun create(qsTileHost: QSTileHost): TileServiceRequestController {
+        fun create(qsHost: QSHost): TileServiceRequestController {
             return TileServiceRequestController(
-                    qsTileHost,
+                    qsHost,
                     commandQueue,
                     commandRegistry,
                     TileRequestDialogEventLogger()
             )
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 84a18d8..adc7165 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -39,7 +39,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -68,22 +68,24 @@
     private final Context mContext;
     private final Handler mMainHandler;
     private final Provider<Handler> mHandlerProvider;
-    private final QSTileHost mHost;
+    private final QSHost mHost;
     private final KeyguardStateController mKeyguardStateController;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final CommandQueue mCommandQueue;
     private final UserTracker mUserTracker;
+    private final StatusBarIconController mStatusBarIconController;
 
     private int mMaxBound = DEFAULT_MAX_BOUND;
 
     @Inject
     public TileServices(
-            QSTileHost host,
+            QSHost host,
             @Main Provider<Handler> handlerProvider,
             BroadcastDispatcher broadcastDispatcher,
             UserTracker userTracker,
             KeyguardStateController keyguardStateController,
-            CommandQueue commandQueue) {
+            CommandQueue commandQueue,
+            StatusBarIconController statusBarIconController) {
         mHost = host;
         mKeyguardStateController = keyguardStateController;
         mContext = mHost.getContext();
@@ -92,6 +94,7 @@
         mMainHandler = mHandlerProvider.get();
         mUserTracker = userTracker;
         mCommandQueue = commandQueue;
+        mStatusBarIconController = statusBarIconController;
         mCommandQueue.addCallback(mRequestListeningCallback);
     }
 
@@ -99,7 +102,7 @@
         return mContext;
     }
 
-    public QSTileHost getHost() {
+    public QSHost getHost() {
         return mHost;
     }
 
@@ -131,8 +134,7 @@
             mTiles.remove(tile.getComponent());
             final String slot = tile.getComponent().getClassName();
             // TileServices doesn't know how to add more than 1 icon per slot, so remove all
-            mMainHandler.post(() -> mHost.getIconController()
-                    .removeAllIconsForExternalSlot(slot));
+            mMainHandler.post(() -> mStatusBarIconController.removeAllIconsForSlot(slot));
         }
     }
 
@@ -309,7 +311,7 @@
                     mMainHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            StatusBarIconController iconController = mHost.getIconController();
+                            StatusBarIconController iconController = mStatusBarIconController;
                             iconController.setIcon(componentName.getClassName(), statusIcon);
                             iconController.setExternalIcon(componentName.getClassName());
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 9b4ac1b..a915ddb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -18,6 +18,7 @@
 import android.content.Intent
 import android.os.Handler
 import android.os.Looper
+import android.provider.Settings
 import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.MetricsLogger
@@ -72,9 +73,7 @@
     }
 
     override fun newTileState(): QSTile.State {
-        val state = QSTile.State()
-        state.handlesLongClick = false
-        return state
+        return QSTile.State()
     }
 
     override fun handleClick(view: View?) {
@@ -98,7 +97,7 @@
     }
 
     override fun getLongClickIntent(): Intent? {
-        return null
+        return Intent(Settings.ACTION_TEXT_READING_SETTINGS)
     }
 
     override fun getTileLabel(): CharSequence {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 50f3db3..099ad94 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -193,7 +193,12 @@
     private boolean mAnimatingHiddenFromCollapsed;
     private boolean mVisible;
     private float mExpansionHeight;
+    /**
+     * QS height when QS expansion fraction is 0 so when QS is collapsed. That state doesn't really
+     * exist for split shade so currently this value is always 0 then.
+     */
     private int mMinExpansionHeight;
+    /** QS height when QS expansion fraction is 1 so qs is fully expanded */
     private int mMaxExpansionHeight;
     /** Expansion fraction of the notification shade */
     private float mShadeExpandedFraction;
@@ -697,6 +702,7 @@
 
     /** update Qs height state */
     public void setExpansionHeight(float height) {
+        checkCorrectSplitShadeState(height);
         int maxHeight = getMaxExpansionHeight();
         height = Math.min(Math.max(
                 height, getMinExpansionHeight()), maxHeight);
@@ -718,6 +724,14 @@
         }
     }
 
+    /** TODO(b/269742565) Remove this logging */
+    private void checkCorrectSplitShadeState(float height) {
+        if (mSplitShadeEnabled && height == 0
+                && mPanelViewControllerLazy.get().isShadeFullyOpen()) {
+            Log.wtfStack(TAG, "qsExpansion set to 0 while split shade is expanding or open");
+        }
+    }
+
     /** */
     public void setHeightOverrideToDesiredHeight() {
         if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 84b40e0..77d98d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -234,7 +234,7 @@
 
     /**
      * @param key
-     * @return true if the entry is pinned
+     * @return true if the entry is (pinned and expanded) or (has an active remote input)
      */
     public boolean isSticky(String key) {
         AlertEntry alerting = mAlertEntries.get(key);
@@ -256,15 +256,6 @@
         return 0;
     }
 
-    @VisibleForTesting
-    public long getCalculatedEarliestRemovalTime(String key) {
-        AlertEntry alerting = mAlertEntries.get(key);
-        if (alerting != null) {
-            return alerting.mEarliestRemovaltime;
-        }
-        return 0;
-    }
-
     protected class AlertEntry implements Comparable<AlertEntry> {
         @Nullable public NotificationEntry mEntry;
         public long mPostTime;
@@ -285,11 +276,6 @@
             updateEntry(true /* updatePostTime */);
         }
 
-        @VisibleForTesting
-        long getEarliestRemovaltime() {
-            return mEarliestRemovaltime;
-        }
-
         /**
          * Updates an entry's removal time.
          * @param updatePostTime whether or not to refresh the post time
@@ -305,23 +291,26 @@
             }
             removeAutoRemovalCallbacks();
 
-            final long finishTime = calculateFinishTime();
-            final long timeRemaining = isSticky()
-                    ? finishTime - mClock.currentTimeMillis()
-                    : Math.max(finishTime - now, mMinimumDisplayTime);
-
-            mHandler.postDelayed(mRemoveAlertRunnable, timeRemaining);
+            if (!isSticky()) {
+                final long finishTime = calculateFinishTime();
+                final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+                mHandler.postDelayed(mRemoveAlertRunnable, timeLeft);
+            }
         }
 
         /**
          * Whether or not the notification is "sticky" i.e. should stay on screen regardless
-         * of the timer and should be removed externally.
+         * of the timer (forever) and should be removed externally.
          * @return true if the notification is sticky
          */
         public boolean isSticky() {
             // This implementation is overridden by HeadsUpManager HeadsUpEntry #isSticky
-            // but we keep this here for use by unit tests.
-            return mEntry.isStickyAndNotDemoted();
+            return false;
+        }
+
+        public boolean isStickyForSomeTime() {
+            // This implementation is overridden by HeadsUpManager HeadsUpEntry #isStickyForSomeTime
+            return false;
         }
 
         /**
@@ -360,8 +349,9 @@
         public void removeAsSoonAsPossible() {
             if (mRemoveAlertRunnable != null) {
                 removeAutoRemovalCallbacks();
-                mHandler.postDelayed(mRemoveAlertRunnable,
-                        mEarliestRemovaltime - mClock.currentTimeMillis());
+
+                final long timeLeft = mEarliestRemovaltime - mClock.currentTimeMillis();
+                mHandler.postDelayed(mRemoveAlertRunnable, timeLeft);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
new file mode 100644
index 0000000..37140ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.annotation.IntRange
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.Configuration
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.statusbar.events.BackgroundAnimatableView
+
+class BatteryStatusChip @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    FrameLayout(context, attrs), BackgroundAnimatableView {
+
+    private val roundedContainer: LinearLayout
+    private val batteryMeterView: BatteryMeterView
+    override val contentView: View
+        get() = batteryMeterView
+
+    init {
+        inflate(context, R.layout.battery_status_chip, this)
+        roundedContainer = findViewById(R.id.rounded_container)
+        batteryMeterView = findViewById(R.id.battery_meter_view)
+        updateResources()
+    }
+
+    /**
+     * When animating as a chip in the status bar, we want to animate the width for the rounded
+     * container. We have to subtract our own top and left offset because the bounds come to us as
+     * absolute on-screen bounds.
+     */
+    override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
+        roundedContainer.setLeftTopRightBottom(l - left, t - top, r - left, b - top)
+    }
+
+    fun setBatteryLevel(@IntRange(from = 0, to = 100) batteryLevel: Int) {
+        batteryMeterView.setForceShowPercent(true)
+        batteryMeterView.onBatteryLevelChanged(batteryLevel, true)
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        updateResources()
+    }
+
+    @SuppressLint("UseCompatLoadingForDrawables")
+    private fun updateResources() {
+        val primaryColor =
+            Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+        val textColorSecondary =
+            Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorSecondary)
+        batteryMeterView.updateColors(primaryColor, textColorSecondary, primaryColor)
+        roundedContainer.background = mContext.getDrawable(R.drawable.statusbar_chip_bg)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 2cf1f53..c435799 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -168,6 +168,7 @@
     private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
     private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
     private static final int MSG_SHOW_MEDIA_OUTPUT_SWITCHER = 72 << MSG_SHIFT;
+    private static final int MSG_TOGGLE_TASKBAR = 73 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -229,6 +230,7 @@
                 @BackDispositionMode int backDisposition, boolean showImeSwitcher) { }
         default void showRecentApps(boolean triggeredFromAltTab) { }
         default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
+        default void toggleTaskbar() { }
         default void toggleRecentApps() { }
         default void toggleSplitScreen() { }
         default void preloadRecentApps() { }
@@ -715,6 +717,13 @@
         }
     }
 
+    public void toggleTaskbar() {
+        synchronized (mLock) {
+            mHandler.removeMessages(MSG_TOGGLE_TASKBAR);
+            mHandler.obtainMessage(MSG_TOGGLE_TASKBAR, 0, 0, null).sendToTarget();
+        }
+    }
+
     public void toggleRecentApps() {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_TOGGLE_RECENT_APPS);
@@ -1416,6 +1425,11 @@
                         mCallbacks.get(i).hideRecentApps(msg.arg1 != 0, msg.arg2 != 0);
                     }
                     break;
+                case MSG_TOGGLE_TASKBAR:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).toggleTaskbar();
+                    }
+                    break;
                 case MSG_TOGGLE_RECENT_APPS:
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).toggleRecentApps();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index f4cd985..51c5183 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -213,7 +213,8 @@
             DeviceProvisionedController deviceProvisionedController,
             KeyguardStateController keyguardStateController,
             SecureSettings secureSettings,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            LockPatternUtils lockPatternUtils) {
         mContext = context;
         mMainHandler = mainHandler;
         mDevicePolicyManager = devicePolicyManager;
@@ -225,7 +226,7 @@
         mClickNotifier = clickNotifier;
         mOverviewProxyServiceLazy = overviewProxyServiceLazy;
         statusBarStateController.addCallback(this);
-        mLockPatternUtils = new LockPatternUtils(context);
+        mLockPatternUtils = lockPatternUtils;
         mKeyguardManager = keyguardManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mDeviceProvisionedController = deviceProvisionedController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index bd5b8f0..bfc4e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.events
 
-import android.animation.Animator
+import androidx.core.animation.Animator
 import android.annotation.UiThread
 import android.graphics.Point
 import android.graphics.Rect
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
new file mode 100644
index 0000000..3d6d489
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineScope
+
+@Module
+interface StatusBarEventsModule {
+
+    companion object {
+
+        @Provides
+        @SysUISingleton
+        fun provideSystemStatusAnimationScheduler(
+                featureFlags: FeatureFlags,
+                coordinator: SystemEventCoordinator,
+                chipAnimationController: SystemEventChipAnimationController,
+                statusBarWindowController: StatusBarWindowController,
+                dumpManager: DumpManager,
+                systemClock: SystemClock,
+                @Application coroutineScope: CoroutineScope,
+                @Main executor: DelayableExecutor
+        ): SystemStatusAnimationScheduler {
+            return if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) {
+                SystemStatusAnimationSchedulerImpl(
+                        coordinator,
+                        chipAnimationController,
+                        statusBarWindowController,
+                        dumpManager,
+                        systemClock,
+                        coroutineScope
+                )
+            } else {
+                SystemStatusAnimationSchedulerLegacyImpl(
+                        coordinator,
+                        chipAnimationController,
+                        statusBarWindowController,
+                        dumpManager,
+                        systemClock,
+                        executor
+                )
+            }
+        }
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index 4e1404d..43f78c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -16,24 +16,21 @@
 
 package com.android.systemui.statusbar.events
 
+import android.annotation.IntRange
 import android.annotation.SuppressLint
 import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
-import android.view.LayoutInflater
 import android.view.View
 import android.widget.ImageView
-import com.android.settingslib.graph.ThemedBatteryDrawable
-import com.android.systemui.R
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.statusbar.BatteryStatusChip
 
 typealias ViewCreator = (context: Context) -> BackgroundAnimatableView
 
 interface StatusEvent {
     val priority: Int
     // Whether or not to force the status bar open and show a dot
-    val forceVisible: Boolean
+    var forceVisible: Boolean
     // Whether or not to show an animation for this event
     val showAnimation: Boolean
     val viewCreator: ViewCreator
@@ -73,17 +70,16 @@
     }
 }
 
-class BatteryEvent : StatusEvent {
+class BatteryEvent(@IntRange(from = 0, to = 100) val batteryLevel: Int) : StatusEvent {
     override val priority = 50
-    override val forceVisible = false
+    override var forceVisible = false
     override val showAnimation = true
     override var contentDescription: String? = ""
 
-    override val viewCreator: (context: Context) -> BGImageView = { context ->
-        val iv = BGImageView(context)
-        iv.setImageDrawable(ThemedBatteryDrawable(context, Color.WHITE))
-        iv.setBackgroundDrawable(ColorDrawable(Color.GREEN))
-        iv
+    override val viewCreator: ViewCreator = { context ->
+        BatteryStatusChip(context).apply {
+            setBatteryLevel(batteryLevel)
+        }
     }
 
     override fun toString(): String {
@@ -94,13 +90,12 @@
 class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
     override var contentDescription: String? = null
     override val priority = 100
-    override val forceVisible = true
+    override var forceVisible = true
     var privacyItems: List<PrivacyItem> = listOf()
     private var privacyChip: OngoingPrivacyChip? = null
 
     override val viewCreator: ViewCreator = { context ->
-        val v = LayoutInflater.from(context)
-                .inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip
+        val v = OngoingPrivacyChip(context)
         v.privacyList = privacyItems
         v.contentDescription = contentDescription
         privacyChip = v
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index 8405aea..776956a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -16,10 +16,6 @@
 
 package com.android.systemui.statusbar.events
 
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.AnimatorSet
-import android.animation.ValueAnimator
 import android.content.Context
 import android.graphics.Rect
 import android.view.ContextThemeWrapper
@@ -30,7 +26,13 @@
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorListenerAdapter
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.ValueAnimator
 import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.animation.AnimationUtil.Companion.frames
@@ -43,7 +45,8 @@
 class SystemEventChipAnimationController @Inject constructor(
     private val context: Context,
     private val statusBarWindowController: StatusBarWindowController,
-    private val contentInsetsProvider: StatusBarContentInsetsProvider
+    private val contentInsetsProvider: StatusBarContentInsetsProvider,
+    private val featureFlags: FeatureFlags
 ) : SystemStatusAnimationCallback {
 
     private lateinit var animationWindowView: FrameLayout
@@ -53,12 +56,14 @@
 
     // Left for LTR, Right for RTL
     private var animationDirection = LEFT
-    private var chipRight = 0
-    private var chipLeft = 0
-    private var chipWidth = 0
+    private var chipBounds = Rect()
+    private val chipWidth get() = chipBounds.width()
+    private val chipRight get() = chipBounds.right
+    private val chipLeft get() = chipBounds.left
     private var chipMinWidth = context.resources.getDimensionPixelSize(
             R.dimen.ongoing_appops_chip_min_animation_width)
-    private var dotSize = context.resources.getDimensionPixelSize(
+
+    private val dotSize = context.resources.getDimensionPixelSize(
             R.dimen.ongoing_appops_dot_diameter)
     // Use during animation so that multiple animators can update the drawing rect
     private var animRect = Rect()
@@ -90,21 +95,26 @@
             it.view.measure(
                     View.MeasureSpec.makeMeasureSpec(
                             (animationWindowView.parent as View).width, AT_MOST),
-                    View.MeasureSpec.makeMeasureSpec(animationWindowView.height, AT_MOST))
-            chipWidth = it.chipWidth
-        }
+                    View.MeasureSpec.makeMeasureSpec(
+                            (animationWindowView.parent as View).height, AT_MOST))
 
-        // decide which direction we're animating from, and then set some screen coordinates
-        val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
-        when (animationDirection) {
-            LEFT -> {
-                chipRight = contentRect.right
-                chipLeft = contentRect.right - chipWidth
+            // decide which direction we're animating from, and then set some screen coordinates
+            val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
+            val chipTop = ((animationWindowView.parent as View).height - it.view.measuredHeight) / 2
+            val chipBottom = chipTop + it.view.measuredHeight
+            val chipRight: Int
+            val chipLeft: Int
+            when (animationDirection) {
+                LEFT -> {
+                    chipRight = contentRect.right
+                    chipLeft = contentRect.right - it.chipWidth
+                }
+                else /* RIGHT */ -> {
+                    chipLeft = contentRect.left
+                    chipRight = contentRect.left + it.chipWidth
+                }
             }
-            else /* RIGHT */ -> {
-                chipLeft = contentRect.left
-                chipRight = contentRect.left + chipWidth
-            }
+            chipBounds = Rect(chipLeft, chipTop, chipRight, chipBottom)
         }
     }
 
@@ -117,16 +127,21 @@
             interpolator = null
             addUpdateListener { currentAnimatedView?.view?.alpha = animatedValue as Float }
         }
+        currentAnimatedView?.contentView?.alpha = 0f
+        val contentAlphaIn = ValueAnimator.ofFloat(0f, 1f).apply {
+            startDelay = 10.frames
+            duration = 10.frames
+            interpolator = null
+            addUpdateListener { currentAnimatedView?.contentView?.alpha = animatedValue as Float }
+        }
         val moveIn = ValueAnimator.ofInt(chipMinWidth, chipWidth).apply {
             startDelay = 7.frames
             duration = 23.frames
             interpolator = STATUS_BAR_X_MOVE_IN
-            addUpdateListener {
-                updateAnimatedViewBoundsWidth(animatedValue as Int)
-            }
+            addUpdateListener { updateAnimatedViewBoundsWidth(animatedValue as Int) }
         }
         val animSet = AnimatorSet()
-        animSet.playTogether(alphaIn, moveIn)
+        animSet.playTogether(alphaIn, contentAlphaIn, moveIn)
         return animSet
     }
 
@@ -139,7 +154,7 @@
         }
 
         finish.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator?) {
+            override fun onAnimationEnd(animation: Animator) {
                 animationWindowView.removeView(currentAnimatedView!!.view)
             }
         })
@@ -152,7 +167,7 @@
             duration = 9.frames
             interpolator = STATUS_CHIP_WIDTH_TO_DOT_KEYFRAME_1
             addUpdateListener {
-                updateAnimatedViewBoundsWidth(it.animatedValue as Int)
+                updateAnimatedViewBoundsWidth(animatedValue as Int)
             }
         }
 
@@ -161,7 +176,7 @@
             duration = 20.frames
             interpolator = STATUS_CHIP_WIDTH_TO_DOT_KEYFRAME_2
             addUpdateListener {
-                updateAnimatedViewBoundsWidth(it.animatedValue as Int)
+                updateAnimatedViewBoundsWidth(animatedValue as Int)
             }
         }
 
@@ -174,7 +189,7 @@
             duration = 6.frames
             interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_1
             addUpdateListener {
-                updateAnimatedViewBoundsHeight(it.animatedValue as Int, chipVerticalCenter)
+                updateAnimatedViewBoundsHeight(animatedValue as Int, chipVerticalCenter)
             }
         }
 
@@ -183,7 +198,7 @@
             duration = 15.frames
             interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_2
             addUpdateListener {
-                updateAnimatedViewBoundsHeight(it.animatedValue as Int, chipVerticalCenter)
+                updateAnimatedViewBoundsHeight(animatedValue as Int, chipVerticalCenter)
             }
         }
 
@@ -210,15 +225,32 @@
     }
 
     private fun createMoveOutAnimationDefault(): Animator {
+        val alphaOut = ValueAnimator.ofFloat(1f, 0f).apply {
+            startDelay = 6.frames
+            duration = 6.frames
+            interpolator = null
+            addUpdateListener { currentAnimatedView?.view?.alpha = animatedValue as Float }
+        }
+
+        val contentAlphaOut = ValueAnimator.ofFloat(1f, 0f).apply {
+            duration = 5.frames
+            interpolator = null
+            addUpdateListener { currentAnimatedView?.contentView?.alpha = animatedValue as Float }
+        }
+
         val moveOut = ValueAnimator.ofInt(chipWidth, chipMinWidth).apply {
             duration = 23.frames
+            interpolator = STATUS_BAR_X_MOVE_OUT
             addUpdateListener {
                 currentAnimatedView?.apply {
-                    updateAnimatedViewBoundsWidth(it.animatedValue as Int)
+                    updateAnimatedViewBoundsWidth(animatedValue as Int)
                 }
             }
         }
-        return moveOut
+
+        val animSet = AnimatorSet()
+        animSet.playTogether(alphaOut, contentAlphaOut, moveOut)
+        return animSet
     }
 
     private fun init() {
@@ -239,11 +271,15 @@
                 it.marginEnd = marginEnd
             }
 
-    private fun initializeAnimRect() = animRect.set(
-            chipLeft,
-            currentAnimatedView!!.view.top,
-            chipRight,
-            currentAnimatedView!!.view.bottom)
+    private fun initializeAnimRect() = if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) {
+        animRect.set(chipBounds)
+    } else {
+        animRect.set(
+                chipLeft,
+                currentAnimatedView!!.view.top,
+                chipRight,
+                currentAnimatedView!!.view.bottom)
+    }
 
     /**
      * To be called during an animation, sets the width and updates the current animated chip view
@@ -296,6 +332,8 @@
 interface BackgroundAnimatableView {
     val view: View // Since this can't extend View, add a view prop
         get() = this as View
+    val contentView: View? // This will be alpha faded during appear and disappear animation
+        get() = null
     val chipWidth: Int
         get() = view.measuredWidth
     fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index fde5d39..26fd230 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -16,11 +16,14 @@
 
 package com.android.systemui.statusbar.events
 
+import android.annotation.IntRange
 import android.content.Context
 import android.provider.DeviceConfig
 import android.provider.DeviceConfig.NAMESPACE_PRIVACY
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.privacy.PrivacyChipBuilder
 import com.android.systemui.privacy.PrivacyItem
 import com.android.systemui.privacy.PrivacyItemController
@@ -37,21 +40,18 @@
     private val systemClock: SystemClock,
     private val batteryController: BatteryController,
     private val privacyController: PrivacyItemController,
-    private val context: Context
+    private val context: Context,
+    private val featureFlags: FeatureFlags
 ) {
     private lateinit var scheduler: SystemStatusAnimationScheduler
 
     fun startObserving() {
-        /* currently unused
         batteryController.addCallback(batteryStateListener)
-        */
         privacyController.addCallback(privacyStateListener)
     }
 
     fun stopObserving() {
-        /* currently unused
         batteryController.removeCallback(batteryStateListener)
-        */
         privacyController.removeCallback(privacyStateListener)
     }
 
@@ -59,12 +59,14 @@
         this.scheduler = s
     }
 
-    fun notifyPluggedIn() {
-        scheduler.onStatusEvent(BatteryEvent())
+    fun notifyPluggedIn(@IntRange(from = 0, to = 100) batteryLevel: Int) {
+        if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) {
+            scheduler.onStatusEvent(BatteryEvent(batteryLevel))
+        }
     }
 
     fun notifyPrivacyItemsEmpty() {
-        scheduler.setShouldShowPersistentPrivacyIndicator(false)
+        scheduler.removePersistentDot()
     }
 
     fun notifyPrivacyItemsChanged(showAnimation: Boolean = true) {
@@ -79,25 +81,25 @@
     }
 
     private val batteryStateListener = object : BatteryController.BatteryStateChangeCallback {
-        var plugged = false
-        var stateKnown = false
+        private var plugged = false
+        private var stateKnown = false
         override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
             if (!stateKnown) {
                 stateKnown = true
                 plugged = pluggedIn
-                notifyListeners()
+                notifyListeners(level)
                 return
             }
 
             if (plugged != pluggedIn) {
                 plugged = pluggedIn
-                notifyListeners()
+                notifyListeners(level)
             }
         }
 
-        private fun notifyListeners() {
+        private fun notifyListeners(@IntRange(from = 0, to = 100) batteryLevel: Int) {
             // We only care about the plugged in status
-            if (plugged) notifyPluggedIn()
+            if (plugged) notifyPluggedIn(batteryLevel)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
index 197cf56..2a18f1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,302 +16,21 @@
 
 package com.android.systemui.statusbar.events
 
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.AnimatorSet
 import android.annotation.IntDef
-import android.os.Process
-import android.provider.DeviceConfig
-import android.util.Log
-import android.view.animation.PathInterpolator
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.PathInterpolator
 import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.policy.CallbackController
-import com.android.systemui.statusbar.window.StatusBarWindowController
-import com.android.systemui.util.Assert
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.time.SystemClock
-import java.io.PrintWriter
-import javax.inject.Inject
 
-/**
- * Dead-simple scheduler for system status events. Obeys the following principles (all values TBD):
- *      - Avoiding log spam by only allowing 12 events per minute (1event/5s)
- *      - Waits 100ms to schedule any event for debouncing/prioritization
- *      - Simple prioritization: Privacy > Battery > connectivity (encoded in [StatusEvent])
- *      - Only schedules a single event, and throws away lowest priority events
- *
- * There are 4 basic stages of animation at play here:
- *      1. System chrome animation OUT
- *      2. Chip animation IN
- *      3. Chip animation OUT; potentially into a dot
- *      4. System chrome animation IN
- *
- * Thus we can keep all animations synchronized with two separate ValueAnimators, one for system
- * chrome and the other for the chip. These can animate from 0,1 and listeners can parameterize
- * their respective views based on the progress of the animator. Interpolation differences TBD
- */
-@SysUISingleton
-open class SystemStatusAnimationScheduler @Inject constructor(
-    private val coordinator: SystemEventCoordinator,
-    private val chipAnimationController: SystemEventChipAnimationController,
-    private val statusBarWindowController: StatusBarWindowController,
-    private val dumpManager: DumpManager,
-    private val systemClock: SystemClock,
-    @Main private val executor: DelayableExecutor
-) : CallbackController<SystemStatusAnimationCallback>, Dumpable {
+interface SystemStatusAnimationScheduler :
+        CallbackController<SystemStatusAnimationCallback>, Dumpable {
 
-    companion object {
-        private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
-    }
-    public fun isImmersiveIndicatorEnabled(): Boolean {
-        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
-                PROPERTY_ENABLE_IMMERSIVE_INDICATOR, true)
-    }
+    @SystemAnimationState fun getAnimationState(): Int
 
-    @SystemAnimationState var animationState: Int = IDLE
-        private set
+    fun onStatusEvent(event: StatusEvent)
 
-    /** True if the persistent privacy dot should be active */
-    var hasPersistentDot = false
-        protected set
-
-    private var scheduledEvent: StatusEvent? = null
-    private var cancelExecutionRunnable: Runnable? = null
-    private val listeners = mutableSetOf<SystemStatusAnimationCallback>()
-
-    fun getListeners(): MutableSet<SystemStatusAnimationCallback> {
-        return listeners
-    }
-
-    init {
-        coordinator.attachScheduler(this)
-        dumpManager.registerDumpable(TAG, this)
-    }
-
-    open fun onStatusEvent(event: StatusEvent) {
-        // Ignore any updates until the system is up and running
-        if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
-            return
-        }
-
-        // Don't deal with threading for now (no need let's be honest)
-        Assert.isMainThread()
-        if ((event.priority > scheduledEvent?.priority ?: -1) &&
-                animationState != ANIMATING_OUT &&
-                (animationState != SHOWING_PERSISTENT_DOT && event.forceVisible)) {
-            // events can only be scheduled if a higher priority or no other event is in progress
-            if (DEBUG) {
-                Log.d(TAG, "scheduling event $event")
-            }
-
-            scheduleEvent(event)
-        } else if (scheduledEvent?.shouldUpdateFromEvent(event) == true) {
-            if (DEBUG) {
-                Log.d(TAG, "updating current event from: $event. animationState=$animationState")
-            }
-            scheduledEvent?.updateFromEvent(event)
-            if (event.forceVisible) {
-                hasPersistentDot = true
-                // If we missed the chance to show the persistent dot, do it now
-                if (animationState == IDLE) {
-                    notifyTransitionToPersistentDot()
-                }
-            }
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "ignoring event $event")
-            }
-        }
-    }
-
-    private fun clearDotIfVisible() {
-        notifyHidePersistentDot()
-    }
-
-    fun setShouldShowPersistentPrivacyIndicator(should: Boolean) {
-        if (hasPersistentDot == should || !isImmersiveIndicatorEnabled()) {
-            return
-        }
-
-        hasPersistentDot = should
-
-        if (!hasPersistentDot) {
-            clearDotIfVisible()
-        }
-    }
-
-    public fun isTooEarly(): Boolean {
-        return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
-    }
-
-    /**
-     * Clear the scheduled event (if any) and schedule a new one
-     */
-    private fun scheduleEvent(event: StatusEvent) {
-        scheduledEvent = event
-
-        if (event.forceVisible) {
-            hasPersistentDot = true
-        }
-
-        // If animations are turned off, we'll transition directly to the dot
-        if (!event.showAnimation && event.forceVisible) {
-            notifyTransitionToPersistentDot()
-            scheduledEvent = null
-            return
-        }
-
-        chipAnimationController.prepareChipAnimation(scheduledEvent!!.viewCreator)
-        animationState = ANIMATION_QUEUED
-        executor.executeDelayed({
-            runChipAnimation()
-        }, DEBOUNCE_DELAY)
-    }
-
-    /**
-     * 1. Define a total budget for the chip animation (1500ms)
-     * 2. Send out callbacks to listeners so that they can generate animations locally
-     * 3. Update the scheduler state so that clients know where we are
-     * 4. Maybe: provide scaffolding such as: dot location, margins, etc
-     * 5. Maybe: define a maximum animation length and enforce it. Probably only doable if we
-     * collect all of the animators and run them together.
-     */
-    private fun runChipAnimation() {
-        statusBarWindowController.setForceStatusBarVisible(true)
-        animationState = ANIMATING_IN
-
-        val animSet = collectStartAnimations()
-        if (animSet.totalDuration > 500) {
-            throw IllegalStateException("System animation total length exceeds budget. " +
-                    "Expected: 500, actual: ${animSet.totalDuration}")
-        }
-        animSet.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator?) {
-                animationState = RUNNING_CHIP_ANIM
-            }
-        })
-        animSet.start()
-
-        executor.executeDelayed({
-            val animSet2 = collectFinishAnimations()
-            animationState = ANIMATING_OUT
-            animSet2.addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
-                    animationState = if (hasPersistentDot) {
-                        SHOWING_PERSISTENT_DOT
-                    } else {
-                        IDLE
-                    }
-
-                    statusBarWindowController.setForceStatusBarVisible(false)
-                }
-            })
-            animSet2.start()
-            scheduledEvent = null
-        }, DISPLAY_LENGTH)
-    }
-
-    private fun collectStartAnimations(): AnimatorSet {
-        val animators = mutableListOf<Animator>()
-        listeners.forEach { listener ->
-            listener.onSystemEventAnimationBegin()?.let { anim ->
-                animators.add(anim)
-            }
-        }
-        animators.add(chipAnimationController.onSystemEventAnimationBegin())
-        val animSet = AnimatorSet().also {
-            it.playTogether(animators)
-        }
-
-        return animSet
-    }
-
-    private fun collectFinishAnimations(): AnimatorSet {
-        val animators = mutableListOf<Animator>()
-        listeners.forEach { listener ->
-            listener.onSystemEventAnimationFinish(hasPersistentDot)?.let { anim ->
-                animators.add(anim)
-            }
-        }
-        animators.add(chipAnimationController.onSystemEventAnimationFinish(hasPersistentDot))
-        if (hasPersistentDot) {
-            val dotAnim = notifyTransitionToPersistentDot()
-            if (dotAnim != null) {
-                animators.add(dotAnim)
-            }
-        }
-        val animSet = AnimatorSet().also {
-            it.playTogether(animators)
-        }
-
-        return animSet
-    }
-
-    private fun notifyTransitionToPersistentDot(): Animator? {
-        val anims: List<Animator> = listeners.mapNotNull {
-            it.onSystemStatusAnimationTransitionToPersistentDot(scheduledEvent?.contentDescription)
-        }
-        if (anims.isNotEmpty()) {
-            val aSet = AnimatorSet()
-            aSet.playTogether(anims)
-            return aSet
-        }
-
-        return null
-    }
-
-    private fun notifyHidePersistentDot(): Animator? {
-        val anims: List<Animator> = listeners.mapNotNull {
-            it.onHidePersistentDot()
-        }
-
-        if (animationState == SHOWING_PERSISTENT_DOT) {
-            animationState = IDLE
-        }
-
-        if (anims.isNotEmpty()) {
-            val aSet = AnimatorSet()
-            aSet.playTogether(anims)
-            return aSet
-        }
-
-        return null
-    }
-
-    override fun addCallback(listener: SystemStatusAnimationCallback) {
-        Assert.isMainThread()
-
-        if (listeners.isEmpty()) {
-            coordinator.startObserving()
-        }
-        listeners.add(listener)
-    }
-
-    override fun removeCallback(listener: SystemStatusAnimationCallback) {
-        Assert.isMainThread()
-
-        listeners.remove(listener)
-        if (listeners.isEmpty()) {
-            coordinator.stopObserving()
-        }
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println("Scheduled event: $scheduledEvent")
-        pw.println("Has persistent privacy dot: $hasPersistentDot")
-        pw.println("Animation state: $animationState")
-        pw.println("Listeners:")
-        if (listeners.isEmpty()) {
-            pw.println("(none)")
-        } else {
-            listeners.forEach {
-                pw.println("  $it")
-            }
-        }
-    }
+    fun removePersistentDot()
 }
 
 /**
@@ -337,6 +56,7 @@
     @JvmDefault fun onHidePersistentDot(): Animator? { return null }
 }
 
+
 /**
  * Animation state IntDef
  */
@@ -354,7 +74,7 @@
 annotation class SystemAnimationState
 
 /** No animation is in progress */
-const val IDLE = 0
+@SystemAnimationState const val IDLE = 0
 /** An animation is queued, and awaiting the debounce period */
 const val ANIMATION_QUEUED = 1
 /** System is animating out, and chip is animating in */
@@ -379,20 +99,16 @@
 val STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_2 = PathInterpolator(0.3f, 0f, 0f, 1f)
 val STATUS_CHIP_MOVE_TO_DOT = PathInterpolator(0f, 0f, 0.05f, 1f)
 
-private const val TAG = "SystemStatusAnimationScheduler"
-private const val DEBOUNCE_DELAY = 100L
+internal const val DEBOUNCE_DELAY = 100L
 
 /**
  * The total time spent on the chip animation is 1500ms, broken up into 3 sections:
- *   - 500ms to animate the chip in (including animating system icons away)
- *   - 500ms holding the chip on screen
- *   - 500ms to animate the chip away (and system icons back)
- *
- *   So DISPLAY_LENGTH should be the sum of the first 2 phases, while the final 500ms accounts for
- *   the actual animation
+ * - 500ms to animate the chip in (including animating system icons away)
+ * - 500ms holding the chip on screen
+ * - 500ms to animate the chip away (and system icons back)
  */
-private const val DISPLAY_LENGTH = 1000L
+internal const val APPEAR_ANIMATION_DURATION = 500L
+internal const val DISPLAY_LENGTH = 3000L
+internal const val DISAPPEAR_ANIMATION_DURATION = 500L
 
-private const val MIN_UPTIME: Long = 5 * 1000
-
-private const val DEBUG = false
+internal const val MIN_UPTIME: Long = 5 * 1000
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
new file mode 100644
index 0000000..f7a4fea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -0,0 +1,425 @@
+/*
+ * 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.statusbar.events
+
+import android.os.Process
+import android.provider.DeviceConfig
+import android.util.Log
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorListenerAdapter
+import androidx.core.animation.AnimatorSet
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.Assert
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeout
+
+/**
+ * Scheduler for system status events. Obeys the following principles:
+ * ```
+ *      - Waits 100 ms to schedule any event for debouncing/prioritization
+ *      - Simple prioritization: Privacy > Battery > Connectivity (encoded in [StatusEvent])
+ *      - Only schedules a single event, and throws away lowest priority events
+ * ```
+ *
+ * There are 4 basic stages of animation at play here:
+ * ```
+ *      1. System chrome animation OUT
+ *      2. Chip animation IN
+ *      3. Chip animation OUT; potentially into a dot
+ *      4. System chrome animation IN
+ * ```
+ *
+ * Thus we can keep all animations synchronized with two separate ValueAnimators, one for system
+ * chrome and the other for the chip. These can animate from 0,1 and listeners can parameterize
+ * their respective views based on the progress of the animator.
+ */
+@OptIn(FlowPreview::class)
+open class SystemStatusAnimationSchedulerImpl
+@Inject
+constructor(
+    private val coordinator: SystemEventCoordinator,
+    private val chipAnimationController: SystemEventChipAnimationController,
+    private val statusBarWindowController: StatusBarWindowController,
+    dumpManager: DumpManager,
+    private val systemClock: SystemClock,
+    @Application private val coroutineScope: CoroutineScope
+) : SystemStatusAnimationScheduler {
+
+    companion object {
+        private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
+    }
+
+    /** Contains the StatusEvent that is going to be displayed next. */
+    private var scheduledEvent = MutableStateFlow<StatusEvent?>(null)
+
+    /**
+     * The currently displayed status event. (This is null in all states except ANIMATING_IN and
+     * CHIP_ANIMATION_RUNNING)
+     */
+    private var currentlyDisplayedEvent: StatusEvent? = null
+
+    /** StateFlow holding the current [SystemAnimationState] at any time. */
+    private var animationState = MutableStateFlow(IDLE)
+
+    /** True if the persistent privacy dot should be active */
+    var hasPersistentDot = false
+        protected set
+
+    /** Set of currently registered listeners */
+    protected val listeners = mutableSetOf<SystemStatusAnimationCallback>()
+
+    /** The job that is controlling the animators of the currently displayed status event. */
+    private var currentlyRunningAnimationJob: Job? = null
+
+    /** The job that is controlling the animators when an event is cancelled. */
+    private var eventCancellationJob: Job? = null
+
+    init {
+        coordinator.attachScheduler(this)
+        dumpManager.registerCriticalDumpable(TAG, this)
+
+        coroutineScope.launch {
+            // Wait for animationState to become ANIMATION_QUEUED and scheduledEvent to be non null.
+            // Once this combination is stable for at least DEBOUNCE_DELAY, then start a chip enter
+            // animation
+            animationState
+                .combine(scheduledEvent) { animationState, scheduledEvent ->
+                    Pair(animationState, scheduledEvent)
+                }
+                .debounce(DEBOUNCE_DELAY)
+                .collect { (animationState, event) ->
+                    if (animationState == ANIMATION_QUEUED && event != null) {
+                        startAnimationLifecycle(event)
+                        scheduledEvent.value = null
+                    }
+                }
+        }
+    }
+
+    @SystemAnimationState override fun getAnimationState(): Int = animationState.value
+
+    override fun onStatusEvent(event: StatusEvent) {
+        Assert.isMainThread()
+
+        // Ignore any updates until the system is up and running
+        if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+            return
+        }
+
+        if (
+            (event.priority > (scheduledEvent.value?.priority ?: -1)) &&
+                (event.priority > (currentlyDisplayedEvent?.priority ?: -1)) &&
+                !hasPersistentDot
+        ) {
+            // a event can only be scheduled if no other event is in progress or it has a higher
+            // priority. If a persistent dot is currently displayed, don't schedule the event.
+            if (DEBUG) {
+                Log.d(TAG, "scheduling event $event")
+            }
+
+            scheduleEvent(event)
+        } else if (currentlyDisplayedEvent?.shouldUpdateFromEvent(event) == true) {
+            if (DEBUG) {
+                Log.d(
+                    TAG,
+                    "updating current event from: $event. animationState=${animationState.value}"
+                )
+            }
+            currentlyDisplayedEvent?.updateFromEvent(event)
+        } else if (scheduledEvent.value?.shouldUpdateFromEvent(event) == true) {
+            if (DEBUG) {
+                Log.d(
+                    TAG,
+                    "updating scheduled event from: $event. animationState=${animationState.value}"
+                )
+            }
+            scheduledEvent.value?.updateFromEvent(event)
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "ignoring event $event")
+            }
+        }
+    }
+
+    override fun removePersistentDot() {
+        Assert.isMainThread()
+
+        // If there is an event scheduled currently, set its forceVisible flag to false, such that
+        // it will never transform into a persistent dot
+        scheduledEvent.value?.forceVisible = false
+
+        // Nothing else to do if hasPersistentDot is already false
+        if (!hasPersistentDot) return
+        // Set hasPersistentDot to false. If the animationState is anything before ANIMATING_OUT,
+        // the disappear animation will not animate into a dot but remove the chip entirely
+        hasPersistentDot = false
+        // if we are currently showing a persistent dot, hide it
+        if (animationState.value == SHOWING_PERSISTENT_DOT) notifyHidePersistentDot()
+        // if we are currently animating into a dot, wait for the animation to finish and then hide
+        // the dot
+        if (animationState.value == ANIMATING_OUT) {
+            coroutineScope.launch {
+                withTimeout(DISAPPEAR_ANIMATION_DURATION) {
+                    animationState.first { it == SHOWING_PERSISTENT_DOT || it == ANIMATION_QUEUED }
+                    notifyHidePersistentDot()
+                }
+            }
+        }
+    }
+
+    protected fun isTooEarly(): Boolean {
+        return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
+    }
+
+    protected fun isImmersiveIndicatorEnabled(): Boolean {
+        return DeviceConfig.getBoolean(
+            DeviceConfig.NAMESPACE_PRIVACY,
+            PROPERTY_ENABLE_IMMERSIVE_INDICATOR,
+            true
+        )
+    }
+
+    /** Clear the scheduled event (if any) and schedule a new one */
+    private fun scheduleEvent(event: StatusEvent) {
+        scheduledEvent.value = event
+        if (currentlyDisplayedEvent != null && eventCancellationJob?.isActive != true) {
+            // cancel the currently displayed event. As soon as the event is animated out, the
+            // scheduled event will be displayed.
+            cancelCurrentlyDisplayedEvent()
+            return
+        }
+        if (animationState.value == IDLE) {
+            // If we are in IDLE state, set it to ANIMATION_QUEUED now
+            animationState.value = ANIMATION_QUEUED
+        }
+    }
+
+    /**
+     * Cancels the currently displayed event by animating it out. This function should only be
+     * called if the animationState is ANIMATING_IN or RUNNING_CHIP_ANIM, or in other words whenever
+     * currentlyRunningEvent is not null
+     */
+    private fun cancelCurrentlyDisplayedEvent() {
+        eventCancellationJob =
+            coroutineScope.launch {
+                withTimeout(APPEAR_ANIMATION_DURATION) {
+                    // wait for animationState to become RUNNING_CHIP_ANIM, then cancel the running
+                    // animation job and run the disappear animation immediately
+                    animationState.first { it == RUNNING_CHIP_ANIM }
+                    currentlyRunningAnimationJob?.cancel()
+                    runChipDisappearAnimation()
+                }
+            }
+    }
+
+    /**
+     * Takes the currently scheduled Event and (using the coroutineScope) animates it in and out
+     * again after displaying it for DISPLAY_LENGTH ms. This function should only be called if there
+     * is an event scheduled (and currentlyDisplayedEvent is null)
+     */
+    private fun startAnimationLifecycle(event: StatusEvent) {
+        Assert.isMainThread()
+        hasPersistentDot = event.forceVisible
+
+        if (!event.showAnimation && event.forceVisible) {
+            // If animations are turned off, we'll transition directly to the dot
+            animationState.value = SHOWING_PERSISTENT_DOT
+            notifyTransitionToPersistentDot()
+            return
+        }
+
+        currentlyDisplayedEvent = event
+
+        chipAnimationController.prepareChipAnimation(event.viewCreator)
+        currentlyRunningAnimationJob =
+            coroutineScope.launch {
+                runChipAppearAnimation()
+                delay(APPEAR_ANIMATION_DURATION + DISPLAY_LENGTH)
+                runChipDisappearAnimation()
+            }
+    }
+
+    /**
+     * 1. Define a total budget for the chip animation (1500ms)
+     * 2. Send out callbacks to listeners so that they can generate animations locally
+     * 3. Update the scheduler state so that clients know where we are
+     * 4. Maybe: provide scaffolding such as: dot location, margins, etc
+     * 5. Maybe: define a maximum animation length and enforce it. Probably only doable if we
+     *    collect all of the animators and run them together.
+     */
+    private fun runChipAppearAnimation() {
+        Assert.isMainThread()
+        if (hasPersistentDot) {
+            statusBarWindowController.setForceStatusBarVisible(true)
+        }
+        animationState.value = ANIMATING_IN
+
+        val animSet = collectStartAnimations()
+        if (animSet.totalDuration > 500) {
+            throw IllegalStateException(
+                "System animation total length exceeds budget. " +
+                    "Expected: 500, actual: ${animSet.totalDuration}"
+            )
+        }
+        animSet.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    animationState.value = RUNNING_CHIP_ANIM
+                }
+            }
+        )
+        animSet.start()
+    }
+
+    private fun runChipDisappearAnimation() {
+        Assert.isMainThread()
+        val animSet2 = collectFinishAnimations()
+        animationState.value = ANIMATING_OUT
+        animSet2.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    animationState.value =
+                        when {
+                            hasPersistentDot -> SHOWING_PERSISTENT_DOT
+                            scheduledEvent.value != null -> ANIMATION_QUEUED
+                            else -> IDLE
+                        }
+                    statusBarWindowController.setForceStatusBarVisible(false)
+                }
+            }
+        )
+        animSet2.start()
+
+        // currentlyDisplayedEvent is set to null before the animation has ended such that new
+        // events can be scheduled during the disappear animation. We don't want to miss e.g. a new
+        // privacy event being scheduled during the disappear animation, otherwise we could end up
+        // with e.g. an active microphone but no privacy dot being displayed.
+        currentlyDisplayedEvent = null
+    }
+
+    private fun collectStartAnimations(): AnimatorSet {
+        val animators = mutableListOf<Animator>()
+        listeners.forEach { listener ->
+            listener.onSystemEventAnimationBegin()?.let { anim -> animators.add(anim) }
+        }
+        animators.add(chipAnimationController.onSystemEventAnimationBegin())
+
+        return AnimatorSet().also { it.playTogether(animators) }
+    }
+
+    private fun collectFinishAnimations(): AnimatorSet {
+        val animators = mutableListOf<Animator>()
+        listeners.forEach { listener ->
+            listener.onSystemEventAnimationFinish(hasPersistentDot)?.let { anim ->
+                animators.add(anim)
+            }
+        }
+        animators.add(chipAnimationController.onSystemEventAnimationFinish(hasPersistentDot))
+        if (hasPersistentDot) {
+            val dotAnim = notifyTransitionToPersistentDot()
+            if (dotAnim != null) {
+                animators.add(dotAnim)
+            }
+        }
+
+        return AnimatorSet().also { it.playTogether(animators) }
+    }
+
+    private fun notifyTransitionToPersistentDot(): Animator? {
+        val anims: List<Animator> =
+            listeners.mapNotNull {
+                it.onSystemStatusAnimationTransitionToPersistentDot(
+                    currentlyDisplayedEvent?.contentDescription
+                )
+            }
+        if (anims.isNotEmpty()) {
+            val aSet = AnimatorSet()
+            aSet.playTogether(anims)
+            return aSet
+        }
+
+        return null
+    }
+
+    private fun notifyHidePersistentDot(): Animator? {
+        Assert.isMainThread()
+        val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() }
+
+        if (animationState.value == SHOWING_PERSISTENT_DOT) {
+            if (scheduledEvent.value != null) {
+                animationState.value = ANIMATION_QUEUED
+            } else {
+                animationState.value = IDLE
+            }
+        }
+
+        if (anims.isNotEmpty()) {
+            val aSet = AnimatorSet()
+            aSet.playTogether(anims)
+            return aSet
+        }
+
+        return null
+    }
+
+    override fun addCallback(listener: SystemStatusAnimationCallback) {
+        Assert.isMainThread()
+
+        if (listeners.isEmpty()) {
+            coordinator.startObserving()
+        }
+        listeners.add(listener)
+    }
+
+    override fun removeCallback(listener: SystemStatusAnimationCallback) {
+        Assert.isMainThread()
+
+        listeners.remove(listener)
+        if (listeners.isEmpty()) {
+            coordinator.stopObserving()
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("Scheduled event: ${scheduledEvent.value}")
+        pw.println("Currently displayed event: $currentlyDisplayedEvent")
+        pw.println("Has persistent privacy dot: $hasPersistentDot")
+        pw.println("Animation state: ${animationState.value}")
+        pw.println("Listeners:")
+        if (listeners.isEmpty()) {
+            pw.println("(none)")
+        } else {
+            listeners.forEach { pw.println("  $it") }
+        }
+    }
+}
+
+private const val DEBUG = false
+private const val TAG = "SystemStatusAnimationSchedulerImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
new file mode 100644
index 0000000..64b7ac9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import android.os.Process
+import android.provider.DeviceConfig
+import android.util.Log
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorListenerAdapter
+import androidx.core.animation.AnimatorSet
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.Assert
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * Dead-simple scheduler for system status events. Obeys the following principles (all values TBD):
+ * ```
+ *      - Avoiding log spam by only allowing 12 events per minute (1event/5s)
+ *      - Waits 100ms to schedule any event for debouncing/prioritization
+ *      - Simple prioritization: Privacy > Battery > connectivity (encoded in [StatusEvent])
+ *      - Only schedules a single event, and throws away lowest priority events
+ * ```
+ * There are 4 basic stages of animation at play here:
+ * ```
+ *      1. System chrome animation OUT
+ *      2. Chip animation IN
+ *      3. Chip animation OUT; potentially into a dot
+ *      4. System chrome animation IN
+ * ```
+ * Thus we can keep all animations synchronized with two separate ValueAnimators, one for system
+ * chrome and the other for the chip. These can animate from 0,1 and listeners can parameterize
+ * their respective views based on the progress of the animator. Interpolation differences TBD
+ */
+open class SystemStatusAnimationSchedulerLegacyImpl
+@Inject
+constructor(
+    private val coordinator: SystemEventCoordinator,
+    private val chipAnimationController: SystemEventChipAnimationController,
+    private val statusBarWindowController: StatusBarWindowController,
+    private val dumpManager: DumpManager,
+    private val systemClock: SystemClock,
+    @Main private val executor: DelayableExecutor
+) : SystemStatusAnimationScheduler {
+
+    companion object {
+        private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
+    }
+
+    fun isImmersiveIndicatorEnabled(): Boolean {
+        return DeviceConfig.getBoolean(
+            DeviceConfig.NAMESPACE_PRIVACY,
+            PROPERTY_ENABLE_IMMERSIVE_INDICATOR,
+            true
+        )
+    }
+
+    @SystemAnimationState private var animationState: Int = IDLE
+
+    /** True if the persistent privacy dot should be active */
+    var hasPersistentDot = false
+        protected set
+
+    private var scheduledEvent: StatusEvent? = null
+
+    val listeners = mutableSetOf<SystemStatusAnimationCallback>()
+
+    init {
+        coordinator.attachScheduler(this)
+        dumpManager.registerDumpable(TAG, this)
+    }
+
+    @SystemAnimationState override fun getAnimationState() = animationState
+
+    override fun onStatusEvent(event: StatusEvent) {
+        // Ignore any updates until the system is up and running
+        if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+            return
+        }
+
+        // Don't deal with threading for now (no need let's be honest)
+        Assert.isMainThread()
+        if (
+            (event.priority > (scheduledEvent?.priority ?: -1)) &&
+                animationState != ANIMATING_OUT &&
+                animationState != SHOWING_PERSISTENT_DOT
+        ) {
+            // events can only be scheduled if a higher priority or no other event is in progress
+            if (DEBUG) {
+                Log.d(TAG, "scheduling event $event")
+            }
+
+            scheduleEvent(event)
+        } else if (scheduledEvent?.shouldUpdateFromEvent(event) == true) {
+            if (DEBUG) {
+                Log.d(TAG, "updating current event from: $event. animationState=$animationState")
+            }
+            scheduledEvent?.updateFromEvent(event)
+            if (event.forceVisible) {
+                hasPersistentDot = true
+                // If we missed the chance to show the persistent dot, do it now
+                if (animationState == IDLE) {
+                    notifyTransitionToPersistentDot()
+                }
+            }
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "ignoring event $event")
+            }
+        }
+    }
+
+    override fun removePersistentDot() {
+        if (!hasPersistentDot || !isImmersiveIndicatorEnabled()) {
+            return
+        }
+
+        hasPersistentDot = false
+        notifyHidePersistentDot()
+        return
+    }
+
+    fun isTooEarly(): Boolean {
+        return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
+    }
+
+    /** Clear the scheduled event (if any) and schedule a new one */
+    private fun scheduleEvent(event: StatusEvent) {
+        scheduledEvent = event
+
+        if (event.forceVisible) {
+            hasPersistentDot = true
+        }
+
+        // If animations are turned off, we'll transition directly to the dot
+        if (!event.showAnimation && event.forceVisible) {
+            notifyTransitionToPersistentDot()
+            scheduledEvent = null
+            return
+        }
+
+        chipAnimationController.prepareChipAnimation(scheduledEvent!!.viewCreator)
+        animationState = ANIMATION_QUEUED
+        executor.executeDelayed({ runChipAnimation() }, DEBOUNCE_DELAY)
+    }
+
+    /**
+     * 1. Define a total budget for the chip animation (1500ms)
+     * 2. Send out callbacks to listeners so that they can generate animations locally
+     * 3. Update the scheduler state so that clients know where we are
+     * 4. Maybe: provide scaffolding such as: dot location, margins, etc
+     * 5. Maybe: define a maximum animation length and enforce it. Probably only doable if we
+     * collect all of the animators and run them together.
+     */
+    private fun runChipAnimation() {
+        statusBarWindowController.setForceStatusBarVisible(true)
+        animationState = ANIMATING_IN
+
+        val animSet = collectStartAnimations()
+        if (animSet.totalDuration > 500) {
+            throw IllegalStateException(
+                "System animation total length exceeds budget. " +
+                    "Expected: 500, actual: ${animSet.totalDuration}"
+            )
+        }
+        animSet.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    animationState = RUNNING_CHIP_ANIM
+                }
+            }
+        )
+        animSet.start()
+
+        executor.executeDelayed(
+            {
+                val animSet2 = collectFinishAnimations()
+                animationState = ANIMATING_OUT
+                animSet2.addListener(
+                    object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator) {
+                            animationState =
+                                if (hasPersistentDot) {
+                                    SHOWING_PERSISTENT_DOT
+                                } else {
+                                    IDLE
+                                }
+
+                            statusBarWindowController.setForceStatusBarVisible(false)
+                        }
+                    }
+                )
+                animSet2.start()
+                scheduledEvent = null
+            },
+            DISPLAY_LENGTH
+        )
+    }
+
+    private fun collectStartAnimations(): AnimatorSet {
+        val animators = mutableListOf<Animator>()
+        listeners.forEach { listener ->
+            listener.onSystemEventAnimationBegin()?.let { anim -> animators.add(anim) }
+        }
+        animators.add(chipAnimationController.onSystemEventAnimationBegin())
+        val animSet = AnimatorSet().also { it.playTogether(animators) }
+
+        return animSet
+    }
+
+    private fun collectFinishAnimations(): AnimatorSet {
+        val animators = mutableListOf<Animator>()
+        listeners.forEach { listener ->
+            listener.onSystemEventAnimationFinish(hasPersistentDot)?.let { anim ->
+                animators.add(anim)
+            }
+        }
+        animators.add(chipAnimationController.onSystemEventAnimationFinish(hasPersistentDot))
+        if (hasPersistentDot) {
+            val dotAnim = notifyTransitionToPersistentDot()
+            if (dotAnim != null) {
+                animators.add(dotAnim)
+            }
+        }
+        val animSet = AnimatorSet().also { it.playTogether(animators) }
+
+        return animSet
+    }
+
+    private fun notifyTransitionToPersistentDot(): Animator? {
+        val anims: List<Animator> =
+            listeners.mapNotNull {
+                it.onSystemStatusAnimationTransitionToPersistentDot(
+                    scheduledEvent?.contentDescription
+                )
+            }
+        if (anims.isNotEmpty()) {
+            val aSet = AnimatorSet()
+            aSet.playTogether(anims)
+            return aSet
+        }
+
+        return null
+    }
+
+    private fun notifyHidePersistentDot(): Animator? {
+        val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() }
+
+        if (animationState == SHOWING_PERSISTENT_DOT) {
+            animationState = IDLE
+        }
+
+        if (anims.isNotEmpty()) {
+            val aSet = AnimatorSet()
+            aSet.playTogether(anims)
+            return aSet
+        }
+
+        return null
+    }
+
+    override fun addCallback(listener: SystemStatusAnimationCallback) {
+        Assert.isMainThread()
+
+        if (listeners.isEmpty()) {
+            coordinator.startObserving()
+        }
+        listeners.add(listener)
+    }
+
+    override fun removeCallback(listener: SystemStatusAnimationCallback) {
+        Assert.isMainThread()
+
+        listeners.remove(listener)
+        if (listeners.isEmpty()) {
+            coordinator.stopObserving()
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("Scheduled event: $scheduledEvent")
+        pw.println("Has persistent privacy dot: $hasPersistentDot")
+        pw.println("Animation state: $animationState")
+        pw.println("Listeners:")
+        if (listeners.isEmpty()) {
+            pw.println("(none)")
+        } else {
+            listeners.forEach { pw.println("  $it") }
+        }
+    }
+}
+
+private const val DEBUG = false
+private const val TAG = "SystemStatusAnimationSchedulerLegacyImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 4e9e88d..bc531da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -619,14 +619,6 @@
         return row.isMediaRow();
     }
 
-    /**
-     * We are a top level child if our parent is the list of notifications duh
-     * @return {@code true} if we're a top level notification
-     */
-    public boolean isTopLevelChild() {
-        return row != null && row.isTopLevelChild();
-    }
-
     public void resetUserExpansion() {
         if (row != null) row.resetUserExpansion();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 67a8a63..6c84fef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.notification.dagger.SilentHeader;
 import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -43,8 +44,8 @@
  * Filters out NotificationEntries based on its Ranking and dozing state.
  * Assigns alerting / silent section based on the importance of the notification entry.
  * We check the NotificationEntry's Ranking for:
- *  - whether the notification's app is suspended or hiding its notifications
- *  - whether DND settings are hiding notifications from ambient display or the notification list
+ * - whether the notification's app is suspended or hiding its notifications
+ * - whether DND settings are hiding notifications from ambient display or the notification list
  */
 @CoordinatorScope
 public class RankingCoordinator implements Coordinator {
@@ -78,6 +79,8 @@
     public void attach(NotifPipeline pipeline) {
         mStatusBarStateController.addCallback(mStatusBarStateCallback);
         mSectionStyleProvider.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner));
+        mSectionStyleProvider.setSilentSections(
+                Arrays.asList(mSilentNotifSectioner, mMinimizedNotifSectioner));
 
         pipeline.addPreGroupFilter(mSuspendedFilter);
         pipeline.addPreGroupFilter(mDndVisualEffectsFilter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
index 7b94830..5a3edf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.provider
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import javax.inject.Inject
@@ -27,6 +28,7 @@
  */
 @SysUISingleton
 class SectionStyleProvider @Inject constructor() {
+    private lateinit var silentSections: Set<NotifSectioner>
     private lateinit var lowPrioritySections: Set<NotifSectioner>
 
     /**
@@ -38,9 +40,42 @@
     }
 
     /**
-     * Determine if the given section is minimized
+     * Determine if the given section is minimized.
      */
     fun isMinimizedSection(section: NotifSection): Boolean {
         return lowPrioritySections.contains(section.sectioner)
     }
+
+    /**
+     * Determine if the given entry is minimized.
+     */
+    @JvmOverloads
+    fun isMinimized(entry: ListEntry, ifNotInSection: Boolean = true): Boolean {
+        val section = entry.section ?: return ifNotInSection
+        return isMinimizedSection(section)
+    }
+
+    /**
+     * Feed the provider the information it needs about which sections are silent, so that it can
+     * calculate which entries are in a "silent" section.
+     */
+    fun setSilentSections(sections: Collection<NotifSectioner>) {
+        silentSections = sections.toSet()
+    }
+
+    /**
+     * Determine if the given section is silent.
+     */
+    fun isSilentSection(section: NotifSection): Boolean {
+        return silentSections.contains(section.sectioner)
+    }
+
+    /**
+     * Determine if the given entry is silent.
+     */
+    @JvmOverloads
+    fun isSilent(entry: ListEntry, ifNotInSection: Boolean = true): Boolean {
+        val section = entry.section ?: return ifNotInSection
+        return isSilentSection(section)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index f0b221d..0de3246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -344,7 +344,7 @@
             or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
 }
 
-class ChannelEditorDialog(context: Context) : Dialog(context) {
+class ChannelEditorDialog(context: Context, theme: Int) : Dialog(context, theme) {
     fun updateDoneButtonText(hasChanges: Boolean) {
         findViewById<TextView>(R.id.done_button)?.setText(
                 if (hasChanges)
@@ -361,7 +361,7 @@
         }
 
         fun build(): ChannelEditorDialog {
-            return ChannelEditorDialog(context)
+            return ChannelEditorDialog(context, R.style.Theme_SystemUI_Dialog)
         }
     }
 }
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 7c6efe4..2affa77 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
@@ -110,7 +110,6 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.SwipeableView;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -3587,10 +3586,6 @@
         return mEntry.getSbn().getNotification().isMediaNotification();
     }
 
-    public boolean isTopLevelChild() {
-        return getParent() instanceof NotificationStackScrollLayout;
-    }
-
     public boolean isGroupNotFullyVisible() {
         return getClipTopAmount() > 0 || getTranslationY() < 0;
     }
@@ -3734,7 +3729,9 @@
             }
             pw.println("Roundness: " + getRoundableState().debugString());
 
-            if (mIsSummaryWithChildren) {
+            int transientViewCount = mChildrenContainer == null
+                    ? 0 : mChildrenContainer.getTransientViewCount();
+            if (mIsSummaryWithChildren || transientViewCount > 0) {
                 pw.println();
                 pw.print("ChildrenContainer");
                 pw.print(" visibility: " + mChildrenContainer.getVisibility());
@@ -3742,8 +3739,7 @@
                 pw.print(", translationY: " + mChildrenContainer.getTranslationY());
                 pw.println();
                 List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();
-                pw.println("Children: " + notificationChildren.size());
-                pw.print("{");
+                pw.print("Children: " + notificationChildren.size() + " {");
                 pw.increaseIndent();
                 for (ExpandableNotificationRow child : notificationChildren) {
                     pw.println();
@@ -3751,6 +3747,15 @@
                 }
                 pw.decreaseIndent();
                 pw.println("}");
+                pw.print("Transient Views: " + transientViewCount + " {");
+                pw.increaseIndent();
+                for (int i = 0; i < transientViewCount; i++) {
+                    pw.println();
+                    ExpandableView child = (ExpandableView) mChildrenContainer.getTransientView(i);
+                    child.dump(pw, args);
+                }
+                pw.decreaseIndent();
+                pw.println("}");
             } else if (mPrivateLayout != null) {
                 mPrivateLayout.dumpSmartReplies(pw);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index f1694ac..2dda6fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -317,7 +317,7 @@
         }
         mView.removeChildNotification(childView);
         if (!isTransfer) {
-            mListContainer.notifyGroupChildRemoved(childView, mView);
+            mListContainer.notifyGroupChildRemoved(childView, mView.getChildrenContainer());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index d2087ba6..e09b94b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -2792,7 +2792,7 @@
         }
         child.setOnHeightChangedListener(null);
         updateScrollStateForRemovedChild(child);
-        boolean animationGenerated = generateRemoveAnimation(child);
+        boolean animationGenerated = container != null && generateRemoveAnimation(child);
         if (animationGenerated) {
             if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) {
                 container.addTransientView(child, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 576df7a..f6d53b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -31,7 +31,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.external.CustomTile;
@@ -75,7 +75,7 @@
     private final String mSafetySpec;
 
     protected final Context mContext;
-    protected final QSTileHost mHost;
+    protected final QSHost mHost;
     protected final Handler mHandler;
     protected final SecureSettings mSecureSettings;
     protected final AutoAddTracker mAutoTracker;
@@ -92,7 +92,7 @@
     private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
 
     public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder,
-            QSTileHost host,
+            QSHost host,
             @Background Handler handler,
             SecureSettings secureSettings,
             HotspotController hotspotController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 6f4afe4..b8c7a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -50,6 +50,7 @@
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.CameraLauncher;
@@ -103,6 +104,7 @@
     private final SystemBarAttributesListener mSystemBarAttributesListener;
     private final Lazy<CameraLauncher> mCameraLauncherLazy;
     private final QuickSettingsController mQsController;
+    private final QSHost mQSHost;
 
     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -135,7 +137,8 @@
             @DisplayId int displayId,
             SystemBarAttributesListener systemBarAttributesListener,
             Lazy<CameraLauncher> cameraLauncherLazy,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            QSHost qsHost) {
         mCentralSurfaces = centralSurfaces;
         mQsController = quickSettingsController;
         mContext = context;
@@ -161,6 +164,7 @@
         mDisplayId = displayId;
         mCameraLauncherLazy = cameraLauncherLazy;
         mUserTracker = userTracker;
+        mQSHost = qsHost;
 
         mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
         mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -181,22 +185,17 @@
 
     @Override
     public void addQsTile(ComponentName tile) {
-        QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
-        if (qsPanelController != null && qsPanelController.getHost() != null) {
-            qsPanelController.getHost().addTile(tile);
-        }
+        mQSHost.addTile(tile);
     }
 
     @Override
     public void remQsTile(ComponentName tile) {
-        QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
-        if (qsPanelController != null && qsPanelController.getHost() != null) {
-            qsPanelController.getHost().removeTileByUser(tile);
-        }
+        mQSHost.removeTileByUser(tile);
     }
 
     @Override
     public void clickTile(ComponentName tile) {
+        // Can't inject this because it changes with the QS fragment
         QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
         if (qsPanelController != null) {
             qsPanelController.clickTile(tile);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index cba0897..753032c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -21,9 +21,6 @@
 
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -36,13 +33,16 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.animation.Animator;
+import androidx.core.animation.AnimatorListenerAdapter;
+import androidx.core.animation.ValueAnimator;
 
 import com.android.keyguard.CarrierTextController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.InterpolatorsAndroidX;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.log.LogLevel;
@@ -166,7 +166,8 @@
 
     private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
             animation -> {
-                mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
+                mKeyguardStatusBarAnimateAlpha =
+                        (float) ((ValueAnimator) animation).getAnimatedValue();
                 updateViewState();
             };
 
@@ -434,7 +435,7 @@
         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
         anim.addUpdateListener(mAnimatorUpdateListener);
         anim.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-        anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+        anim.setInterpolator(InterpolatorsAndroidX.LINEAR_OUT_SLOW_IN);
         anim.start();
     }
 
@@ -445,7 +446,7 @@
         anim.addUpdateListener(mAnimatorUpdateListener);
         anim.setStartDelay(startDelay);
         anim.setDuration(duration);
-        anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+        anim.setInterpolator(InterpolatorsAndroidX.LINEAR_OUT_SLOW_IN);
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 85590fc..eb19c0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -1,6 +1,5 @@
 package com.android.systemui.statusbar.phone;
 
-import android.app.NotificationManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Color;
@@ -39,6 +38,7 @@
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -73,6 +73,7 @@
     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
     private final KeyguardBypassController mBypassController;
     private final DozeParameters mDozeParameters;
+    private final SectionStyleProvider mSectionStyleProvider;
     private final Optional<Bubbles> mBubblesOptional;
     private final StatusBarWindowController mStatusBarWindowController;
     private final ScreenOffAnimationController mScreenOffAnimationController;
@@ -117,6 +118,7 @@
             NotificationMediaManager notificationMediaManager,
             NotificationListener notificationListener,
             DozeParameters dozeParameters,
+            SectionStyleProvider sectionStyleProvider,
             Optional<Bubbles> bubblesOptional,
             DemoModeController demoModeController,
             DarkIconDispatcher darkIconDispatcher,
@@ -128,6 +130,7 @@
         mStatusBarStateController.addCallback(this);
         mMediaManager = notificationMediaManager;
         mDozeParameters = dozeParameters;
+        mSectionStyleProvider = sectionStyleProvider;
         mWakeUpCoordinator = wakeUpCoordinator;
         wakeUpCoordinator.addListener(this);
         mBypassController = keyguardBypassController;
@@ -260,19 +263,13 @@
     protected boolean shouldShowNotificationIcon(NotificationEntry entry,
             boolean showAmbient, boolean showLowPriority, boolean hideDismissed,
             boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hidePulsing) {
-        if (entry.getRanking().isAmbient() && !showAmbient) {
+        if (!showAmbient && mSectionStyleProvider.isMinimized(entry)) {
             return false;
         }
         if (hideCurrentMedia && entry.getKey().equals(mMediaManager.getMediaNotificationKey())) {
             return false;
         }
-        if (!showLowPriority && entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
-            return false;
-        }
-        if (!entry.isTopLevelChild()) {
-            return false;
-        }
-        if (entry.getRow().getVisibility() == View.GONE) {
+        if (!showLowPriority && mSectionStyleProvider.isSilent(entry)) {
             return false;
         }
         if (entry.isRowDismissed() && hideDismissed) {
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 65becf7..ed978c3 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
@@ -23,7 +23,6 @@
 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
 
-import android.animation.Animator;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.Fragment;
@@ -43,6 +42,7 @@
 import android.widget.LinearLayout;
 
 import androidx.annotation.VisibleForTesting;
+import androidx.core.animation.Animator;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
index fe69f75..c04ea36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.phone.fragment
 
-import android.animation.Animator
-import android.animation.AnimatorSet
-import android.animation.ValueAnimator
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.ValueAnimator
 import android.content.res.Resources
 import android.view.View
 import com.android.systemui.R
@@ -46,15 +46,19 @@
             R.dimen.ongoing_appops_chip_animation_out_status_bar_translation_x)
 
     override fun onSystemEventAnimationBegin(): Animator {
-        val moveOut = ValueAnimator.ofFloat(0f, 1f).setDuration(23.frames)
-        moveOut.interpolator = STATUS_BAR_X_MOVE_OUT
-        moveOut.addUpdateListener { animation: ValueAnimator ->
-            animatedView.translationX = -(translationXIn * animation.animatedValue as Float)
+        val moveOut = ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = 23.frames
+            interpolator = STATUS_BAR_X_MOVE_OUT
+            addUpdateListener {
+                animatedView.translationX = -(translationXIn * animatedValue as Float)
+            }
         }
-        val alphaOut = ValueAnimator.ofFloat(1f, 0f).setDuration(8.frames)
-        alphaOut.interpolator = null
-        alphaOut.addUpdateListener { animation: ValueAnimator ->
-            animatedView.alpha = animation.animatedValue as Float
+        val alphaOut = ValueAnimator.ofFloat(1f, 0f).apply {
+            duration = 8.frames
+            interpolator = null
+            addUpdateListener {
+                animatedView.alpha = animatedValue as Float
+            }
         }
 
         val animSet = AnimatorSet()
@@ -64,17 +68,21 @@
 
     override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
         animatedView.translationX = translationXOut.toFloat()
-        val moveIn = ValueAnimator.ofFloat(1f, 0f).setDuration(28.frames)
-        moveIn.startDelay = 2.frames
-        moveIn.interpolator = STATUS_BAR_X_MOVE_IN
-        moveIn.addUpdateListener { animation: ValueAnimator ->
-            animatedView.translationX = translationXOut * animation.animatedValue as Float
+        val moveIn = ValueAnimator.ofFloat(1f, 0f).apply {
+            duration = 23.frames
+            startDelay = 7.frames
+            interpolator = STATUS_BAR_X_MOVE_IN
+            addUpdateListener {
+                animatedView.translationX = translationXOut * animatedValue as Float
+            }
         }
-        val alphaIn = ValueAnimator.ofFloat(0f, 1f).setDuration(10.frames)
-        alphaIn.startDelay = 4.frames
-        alphaIn.interpolator = null
-        alphaIn.addUpdateListener { animation: ValueAnimator ->
-            animatedView.alpha = animation.animatedValue as Float
+        val alphaIn = ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = 5.frames
+            startDelay = 11.frames
+            interpolator = null
+            addUpdateListener {
+                animatedView.alpha = animatedValue as Float
+            }
         }
 
         val animatorSet = AnimatorSet()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 3263c4e..1a4a311 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -410,11 +410,14 @@
 
         @Override
         public boolean isSticky() {
-            final boolean isSticky = (mEntry.isRowPinned() && expanded)
+            return (mEntry.isRowPinned() && expanded)
                     || remoteInputActive
-                    || hasFullScreenIntent(mEntry)
-                    || mEntry.isStickyAndNotDemoted();
-            return isSticky;
+                    || hasFullScreenIntent(mEntry);
+        }
+
+        @Override
+        public boolean isStickyForSomeTime() {
+            return mEntry.isStickyAndNotDemoted();
         }
 
         @Override
@@ -476,10 +479,10 @@
          */
         @Override
         protected long calculateFinishTime() {
-            if (isSticky()) {
-                return mEntry.mCreationElapsedRealTime + mStickyDisplayTime;
-            }
-            return mPostTime + getRecommendedHeadsUpTimeoutMs(mAutoDismissNotificationDecay);
+            final long duration = getRecommendedHeadsUpTimeoutMs(
+                    isStickyForSomeTime() ? mStickyDisplayTime : mAutoDismissNotificationDecay);
+
+            return mPostTime + duration;
         }
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index b23d870..8cfe2ea 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -42,7 +42,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -68,7 +68,7 @@
     // Things that use the tunable infrastructure but are now real user settings and
     // shouldn't be reset with tuner settings.
     private static final String[] RESET_EXCEPTION_LIST = new String[] {
-            QSTileHost.TILES_SETTING,
+            QSHost.TILES_SETTING,
             Settings.Secure.DOZE_ALWAYS_ON,
             Settings.Secure.MEDIA_CONTROLS_RESUME,
             Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 5ea4399..0a78d896 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -54,6 +54,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.events.StatusBarEventsModule;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.phone.DozeServiceHost;
@@ -95,6 +96,7 @@
                 PowerModule.class,
                 QSModule.class,
                 ReferenceScreenshotModule.class,
+                StatusBarEventsModule.class,
                 VolumeModule.class,
         }
 )
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
new file mode 100644
index 0000000..30fed0b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.app.ActivityTaskManager
+import android.content.pm.PackageManager
+import android.os.PowerManager
+import android.telecom.TelecomManager
+import android.telephony.TelephonyManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class EmergencyButtonControllerTest : SysuiTestCase() {
+    lateinit var underTest: EmergencyButtonController
+    @Mock lateinit var emergencyButton: EmergencyButton
+    @Mock lateinit var configurationController: ConfigurationController
+    @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock lateinit var telephonyManager: TelephonyManager
+    @Mock lateinit var powerManager: PowerManager
+    @Mock lateinit var activityTaskManager: ActivityTaskManager
+    @Mock lateinit var shadeController: ShadeController
+    @Mock lateinit var telecomManager: TelecomManager
+    @Mock lateinit var metricsLogger: MetricsLogger
+    @Mock lateinit var lockPatternUtils: LockPatternUtils
+    @Mock lateinit var packageManager: PackageManager
+    val fakeSystemClock = FakeSystemClock()
+    val mainExecutor = FakeExecutor(fakeSystemClock)
+    val backgroundExecutor = FakeExecutor(fakeSystemClock)
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            EmergencyButtonController(
+                emergencyButton,
+                configurationController,
+                keyguardUpdateMonitor,
+                telephonyManager,
+                powerManager,
+                activityTaskManager,
+                shadeController,
+                telecomManager,
+                metricsLogger,
+                lockPatternUtils,
+                mainExecutor,
+                backgroundExecutor
+            )
+        context.setMockPackageManager(packageManager)
+        Mockito.`when`(emergencyButton.context).thenReturn(context)
+    }
+
+    @Test
+    fun testUpdateEmergencyButton() {
+        Mockito.`when`(telecomManager.isInCall).thenReturn(true)
+        Mockito.`when`(lockPatternUtils.isSecure(anyInt())).thenReturn(true)
+        Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY))
+            .thenReturn(true)
+        underTest.updateEmergencyCallButton()
+        backgroundExecutor.runAllReady()
+        verify(emergencyButton, never())
+            .updateEmergencyCallButton(
+                /* isInCall= */ any(),
+                /* hasTelephonyRadio= */ any(),
+                /* simLocked= */ any(),
+                /* isSecure= */ any()
+            )
+        mainExecutor.runAllReady()
+        verify(emergencyButton)
+            .updateEmergencyCallButton(
+                /* isInCall= */ eq(true),
+                /* hasTelephonyRadio= */ eq(true),
+                /* simLocked= */ any(),
+                /* isSecure= */ eq(true)
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a1e4f70..395eb8f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -615,9 +615,8 @@
         mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
         mTestableLooper.processAllMessages();
 
-        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
-                anyInt());
-        verify(mFingerprintManager, never()).detectFingerprint(any(), any(), any());
+        verifyFingerprintAuthenticateCall();
+        verifyFingerprintDetectNeverCalled();
     }
 
     @Test
@@ -627,9 +626,8 @@
         mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
         mTestableLooper.processAllMessages();
 
-        verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
-                anyInt(), anyInt());
-        verify(mFingerprintManager, never()).detectFingerprint(any(), any(), any());
+        verifyFingerprintAuthenticateNeverCalled();
+        verifyFingerprintDetectNeverCalled();
     }
 
     @Test
@@ -643,8 +641,8 @@
         mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
         mTestableLooper.processAllMessages();
 
-        verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt());
-        verify(mFingerprintManager).detectFingerprint(any(), any(), any());
+        verifyFingerprintAuthenticateNeverCalled();
+        verifyFingerprintDetectCall();
     }
 
     @Test
@@ -732,9 +730,7 @@
     @Test
     public void testTriesToAuthenticate_whenBouncer() {
         setKeyguardBouncerVisibility(true);
-
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
-        verify(mFaceManager, never()).hasEnrolledTemplates(anyInt());
+        verifyFaceAuthenticateCall();
     }
 
     @Test
@@ -742,7 +738,7 @@
         mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(
                 /* bouncerIsOrWillBeShowing */ true, /* bouncerFullyShown */ false);
 
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateNeverCalled();
     }
 
     @Test
@@ -750,7 +746,8 @@
         keyguardIsVisible();
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+
+        verifyFaceAuthenticateCall();
         verify(mUiEventLogger).logWithInstanceIdAndPosition(
                 eq(FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP),
                 eq(0),
@@ -766,7 +763,7 @@
         mTestableLooper.processAllMessages();
 
         keyguardIsVisible();
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateNeverCalled();
     }
 
     @Test
@@ -777,7 +774,8 @@
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
         keyguardIsVisible();
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+
+        verifyFaceAuthenticateNeverCalled();
     }
 
     @Test
@@ -801,8 +799,8 @@
         mTestableLooper.processAllMessages();
 
         // THEN face detect and authenticate are NOT triggered
-        verify(mFaceManager, never()).detectFace(any(), any(), any());
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceDetectNeverCalled();
+        verifyFaceAuthenticateNeverCalled();
 
         // THEN biometric help message sent to callback
         verify(keyguardUpdateMonitorCallback).onBiometricHelp(
@@ -823,8 +821,8 @@
         mTestableLooper.processAllMessages();
 
         // FACE detect is triggered, not authenticate
-        verify(mFaceManager).detectFace(any(), any(), any());
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceDetectCall();
+        verifyFaceAuthenticateNeverCalled();
 
         // WHEN bouncer becomes visible
         setKeyguardBouncerVisibility(true);
@@ -832,8 +830,8 @@
 
         // THEN face scanning is not run
         mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
-        verify(mFaceManager, never()).detectFace(any(), any(), any());
+        verifyFaceAuthenticateNeverCalled();
+        verifyFaceDetectNeverCalled();
     }
 
     @Test
@@ -848,8 +846,8 @@
         mTestableLooper.processAllMessages();
 
         // FACE detect and authenticate are NOT triggered
-        verify(mFaceManager, never()).detectFace(any(), any(), any());
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceDetectNeverCalled();
+        verifyFaceAuthenticateNeverCalled();
     }
 
     @Test
@@ -887,7 +885,7 @@
         mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
         mKeyguardUpdateMonitor.setAssistantVisible(true);
 
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateCall();
     }
 
     @Test
@@ -895,11 +893,7 @@
         mKeyguardUpdateMonitor.setKeyguardShowing(false, true);
         mKeyguardUpdateMonitor.setAssistantVisible(true);
 
-        verify(mFaceManager, never()).authenticate(any(),
-                any(),
-                any(),
-                any(),
-                anyInt());
+        verifyFaceAuthenticateNeverCalled();
     }
 
     @Test
@@ -910,15 +904,12 @@
 
         // THEN fingerprint shouldn't listen
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
-        verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
-                anyInt(), anyInt());
-
+        verifyFingerprintAuthenticateNeverCalled();
         // WHEN alternate bouncer is shown
         mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
 
         // THEN make sure FP listening begins
-        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
-                anyInt());
+        verifyFingerprintAuthenticateCall();
     }
 
     @Test
@@ -930,7 +921,7 @@
                 KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
                 new ArrayList<>());
         keyguardIsVisible();
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateCall();
     }
 
     @Test
@@ -938,7 +929,7 @@
         mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
         mKeyguardUpdateMonitor.setAssistantVisible(true);
 
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateCall();
         mTestableLooper.processAllMessages();
         clearInvocations(mFaceManager);
 
@@ -951,11 +942,7 @@
         mKeyguardUpdateMonitor.handleKeyguardReset();
 
         assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse();
-        verify(mFaceManager, never()).authenticate(any(),
-                any(),
-                any(),
-                any(),
-                anyInt());
+        verifyFaceAuthenticateNeverCalled();
     }
 
     @Test
@@ -965,7 +952,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
                 KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
         keyguardIsVisible();
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateNeverCalled();
     }
 
     @Test
@@ -977,8 +964,8 @@
         keyguardIsVisible();
         mTestableLooper.processAllMessages();
 
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
-        verify(mFaceManager, never()).detectFace(any(), any(), any());
+        verifyFaceAuthenticateNeverCalled();
+        verifyFaceDetectNeverCalled();
     }
 
     @Test
@@ -987,16 +974,14 @@
                 .onAuthenticationError(FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "");
 
         // THEN doesn't authenticate immediately
-        verify(mFingerprintManager, never()).authenticate(any(),
-                any(), any(), any(), anyInt(), anyInt(), anyInt());
+        verifyFingerprintAuthenticateNeverCalled();
 
         // WHEN all messages (with delays) are processed
         mTestableLooper.moveTimeForward(HAL_POWER_PRESS_TIMEOUT);
         mTestableLooper.processAllMessages();
 
         // THEN fingerprint manager attempts to authenticate again
-        verify(mFingerprintManager).authenticate(any(),
-                any(), any(), any(), anyInt(), anyInt(), anyInt());
+        verifyFingerprintAuthenticateCall();
     }
 
     @Test
@@ -1008,7 +993,7 @@
         setKeyguardBouncerVisibility(true);
         mTestableLooper.processAllMessages();
 
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateNeverCalled();
     }
 
     @Test
@@ -1135,9 +1120,8 @@
         mTestableLooper.processAllMessages();
         keyguardIsVisible();
 
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
-        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
-                anyInt());
+        verifyFaceAuthenticateCall();
+        verifyFingerprintAuthenticateCall();
 
         when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
                 .thenReturn(fingerprintLockoutMode);
@@ -1585,9 +1569,7 @@
     public void testFaceDoesNotAuth_afterPinAttempt() {
         mTestableLooper.processAllMessages();
         mKeyguardUpdateMonitor.setCredentialAttempted();
-        verify(mFingerprintManager, never()).authenticate(any(), any(), any(),
-                any(), anyInt());
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateNeverCalled();
     }
 
     @Test
@@ -1962,9 +1944,8 @@
         mTestableLooper.processAllMessages();
         keyguardIsVisible();
 
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
-        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
-                anyInt());
+        verifyFaceAuthenticateCall();
+        verifyFingerprintAuthenticateCall();
 
         mKeyguardUpdateMonitor.onFaceAuthenticated(0, false);
         // Make sure keyguard is going away after face auth attempt, and that it calls
@@ -1990,8 +1971,7 @@
         mKeyguardUpdateMonitor.dispatchDreamingStopped();
         mTestableLooper.processAllMessages();
 
-        verify(mFaceManager, never()).authenticate(
-                any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateNeverCalled();
     }
 
     @Test
@@ -2004,15 +1984,14 @@
         mTestableLooper.processAllMessages();
 
         // THEN face auth isn't triggered
-        verify(mFaceManager, never()).authenticate(
-                any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateNeverCalled();
 
         // WHEN device wakes up from the power button
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
 
         // THEN face auth is triggered
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateCall();
     }
 
     @Test
@@ -2182,7 +2161,7 @@
         mTestableLooper.processAllMessages();
         keyguardIsVisible();
 
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateCall();
         verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
                 anyInt());
 
@@ -2215,7 +2194,7 @@
         mTestableLooper.processAllMessages();
         keyguardIsVisible();
 
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verifyFaceAuthenticateCall();
 
         final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
         mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
@@ -2486,6 +2465,40 @@
                 eq(false));
     }
 
+    private void verifyFingerprintAuthenticateNeverCalled() {
+        verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyInt(), anyInt());
+    }
+
+    private void verifyFingerprintAuthenticateCall() {
+        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
+                anyInt());
+    }
+
+    private void verifyFingerprintDetectNeverCalled() {
+        verify(mFingerprintManager, never()).detectFingerprint(any(), any(), any());
+    }
+
+    private void verifyFingerprintDetectCall() {
+        verify(mFingerprintManager).detectFingerprint(any(), any(), any());
+    }
+
+    private void verifyFaceAuthenticateNeverCalled() {
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+    }
+
+    private void verifyFaceAuthenticateCall() {
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+    }
+
+    private void verifyFaceDetectNeverCalled() {
+        verify(mFaceManager, never()).detectFace(any(), any(), any());
+    }
+
+    private void verifyFaceDetectCall() {
+        verify(mFaceManager).detectFace(any(), any(), any());
+    }
+
     private void userDeviceLockDown() {
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
         when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
index 777dd4e..ca6f426 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -19,7 +19,7 @@
 import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.widget.ImageView
+import android.view.ViewGroup
 import android.widget.SeekBar
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
@@ -68,14 +68,14 @@
     fun progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScaled() {
         fontScalingDialog.show()
 
-        val iconEnd: ImageView = fontScalingDialog.findViewById(R.id.icon_end)!!
+        val iconEndFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_end_frame)!!
         val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
             fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
         val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
 
         seekBarWithIconButtonsView.setProgress(0)
 
-        iconEnd.performClick()
+        iconEndFrame.performClick()
 
         val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
         assertThat(seekBar.getProgress()).isEqualTo(1)
@@ -88,14 +88,14 @@
     fun progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScaled() {
         fontScalingDialog.show()
 
-        val iconStart: ImageView = fontScalingDialog.findViewById(R.id.icon_start)!!
+        val iconStartFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_start_frame)!!
         val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
             fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
         val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
 
         seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
 
-        iconStart.performClick()
+        iconStartFrame.performClick()
 
         val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
         assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
index bce98cf..0574838 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
@@ -15,27 +15,49 @@
  */
 package com.android.systemui.biometrics
 
+import android.content.Context
 import android.hardware.biometrics.BiometricAuthenticator
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
+import android.hardware.display.DisplayManagerGlobal
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.os.Bundle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import android.view.Surface
 import android.view.View
+import android.view.ViewGroup
 import androidx.test.filters.SmallTest
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenEver
+
+private const val DISPLAY_ID = 2
+private const val SENSOR_ID = 1
 
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
@@ -50,9 +72,22 @@
     private lateinit var callback: AuthBiometricView.Callback
 
     @Mock
+    private lateinit var fingerprintManager: FingerprintManager
+
+    @Mock
+    private lateinit var iconView: LottieAnimationView
+
+    @Mock
+    private lateinit var iconViewOverlay: LottieAnimationView
+
+    @Mock
+    private lateinit var iconLayoutParamSize: Pair<Int, Int>
+
+    @Mock
     private lateinit var panelController: AuthPanelController
 
     private lateinit var biometricView: AuthBiometricView
+    private lateinit var iconController: AuthBiometricFingerprintIconController
 
     private fun createView(allowDeviceCredential: Boolean = false): AuthBiometricFingerprintView {
         val view: AuthBiometricFingerprintView =
@@ -277,5 +312,186 @@
         verify(callback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL)
     }
 
+    private fun testWithSfpsDisplay(
+        isReverseDefaultRotation: Boolean = false,
+        inRearDisplayMode: Boolean = false,
+        isFolded: Boolean = false,
+        initInfo: DisplayInfo.() -> Unit = {},
+        block: () -> Unit
+    ) {
+        val displayInfo = DisplayInfo()
+        displayInfo.initInfo()
+
+        val dmGlobal = mock(DisplayManagerGlobal::class.java)
+        val display = Display(dmGlobal, DISPLAY_ID, displayInfo,
+            DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS)
+
+        whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
+
+        val iconControllerContext = context.createDisplayContext(display) as SysuiTestableContext
+        iconControllerContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_reverseDefaultRotation,
+            isReverseDefaultRotation
+        )
+
+        val rearDisplayDeviceStates = if (inRearDisplayMode) intArrayOf(3) else intArrayOf()
+        iconControllerContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.array.config_rearDisplayDeviceStates,
+            rearDisplayDeviceStates
+        )
+
+        val layoutParams = mock(ViewGroup.LayoutParams::class.java)
+        whenEver(iconView.layoutParams).thenReturn(layoutParams)
+        whenEver(iconViewOverlay.layoutParams).thenReturn(layoutParams)
+
+        var locations = listOf(SensorLocationInternal("", 2500, 0, 0))
+        whenEver(fingerprintManager.sensorPropertiesInternal)
+            .thenReturn(
+                listOf(
+                    FingerprintSensorPropertiesInternal(
+                        SENSOR_ID,
+                        SensorProperties.STRENGTH_STRONG,
+                        5 /* maxEnrollmentsPerUser */,
+                        listOf() /* componentInfo */,
+                        FingerprintSensorProperties.TYPE_POWER_BUTTON,
+                        true /* halControlsIllumination */,
+                        true /* resetLockoutRequiresHardwareAuthToken */,
+                        locations
+                    )
+                )
+            )
+        iconControllerContext.addMockSystemService(Context.FINGERPRINT_SERVICE, fingerprintManager)
+
+        iconController = AuthBiometricFingerprintIconController(
+            iconControllerContext,
+            iconView,
+            iconViewOverlay
+        )
+        iconController.onFoldUpdated(isFolded)
+
+        biometricView.mIconController = iconController
+        block()
+    }
+
+    @Test
+    fun sfpsRearDisplay_showsCorrectAnimationAssetsAcrossRotations() {
+        testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = true,
+            isFolded = false,
+            { rotation = Surface.ROTATION_0 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+        testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = true,
+            isFolded = false,
+            { rotation = Surface.ROTATION_90 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+        testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = true,
+            isFolded = false,
+            { rotation = Surface.ROTATION_180 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+        testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = true,
+            isFolded = false,
+            { rotation = Surface.ROTATION_270 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+        val expectedLottieAssetOrder: List<Int> = listOf(
+            R.raw.biometricprompt_rear_landscape_base,
+            R.raw.biometricprompt_rear_portrait_reverse_base,
+            R.raw.biometricprompt_rear_landscape_base,
+            R.raw.biometricprompt_rear_portrait_base,
+        )
+
+        val lottieAssetCaptor: ArgumentCaptor<Int> = ArgumentCaptor.forClass(Int::class.java)
+        verify(iconView, times(4)).setAnimation(lottieAssetCaptor.capture())
+        val observedLottieAssetOrder: List<Int> = lottieAssetCaptor.getAllValues()
+        assertThat(observedLottieAssetOrder).containsExactlyElementsIn(expectedLottieAssetOrder)
+                .inOrder()
+    }
+
+    @Test
+    fun sfpsDefaultDisplayFolded_showsAnimationsAssetsCorrectlyAcrossRotations() {
+        testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = false,
+            isFolded = true,
+            { rotation = Surface.ROTATION_0 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+            testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = false,
+            isFolded = true,
+            { rotation = Surface.ROTATION_90 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN); }
+            testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = false,
+            isFolded = true,
+            { rotation = Surface.ROTATION_180 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN); }
+            testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = false,
+            isFolded = true,
+            { rotation = Surface.ROTATION_270 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN); }
+        val expectedLottieAssetOrder: List<Int> = listOf(
+            R.raw.biometricprompt_folded_base_default,
+            R.raw.biometricprompt_folded_base_topleft,
+            R.raw.biometricprompt_folded_base_default,
+            R.raw.biometricprompt_folded_base_bottomright,
+        )
+
+        val lottieAssetCaptor: ArgumentCaptor<Int> = ArgumentCaptor.forClass(Int::class.java)
+        verify(iconView, times(4)).setAnimation(lottieAssetCaptor.capture())
+        val observedLottieAssetOrder: List<Int> = lottieAssetCaptor.getAllValues()
+        assertThat(observedLottieAssetOrder).containsExactlyElementsIn(expectedLottieAssetOrder)
+                .inOrder()
+    }
+
+    @Test
+    fun sfpsDefaultDisplayUnfolded_showsAnimationsAssetsCorrectlyAcrossRotations() {
+        testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = false,
+            isFolded = false,
+            { rotation = Surface.ROTATION_0 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+        testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = false,
+            isFolded = false,
+            { rotation = Surface.ROTATION_90 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+        testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = false,
+            isFolded = false,
+            { rotation = Surface.ROTATION_180 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+        testWithSfpsDisplay(
+            isReverseDefaultRotation = false,
+            inRearDisplayMode = false,
+            isFolded = false,
+            { rotation = Surface.ROTATION_270 }
+        ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+        val expectedLottieAssetOrder: List<Int> = listOf(
+            R.raw.biometricprompt_landscape_base,
+            R.raw.biometricprompt_portrait_base_topleft,
+            R.raw.biometricprompt_landscape_base,
+            R.raw.biometricprompt_portrait_base_bottomright,
+        )
+
+        val lottieAssetCaptor: ArgumentCaptor<Int> = ArgumentCaptor.forClass(Int::class.java)
+        verify(iconView, times(4)).setAnimation(lottieAssetCaptor.capture())
+        val observedLottieAssetOrder: List<Int> = lottieAssetCaptor.getAllValues()
+        assertThat(observedLottieAssetOrder).containsExactlyElementsIn(expectedLottieAssetOrder)
+                .inOrder()
+    }
+
     override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
 }
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 612e557..6333a68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -63,6 +63,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -97,6 +98,7 @@
 
     @JvmField @Rule var rule = MockitoJUnit.rule()
 
+    @Mock lateinit var keyguardStateController: KeyguardStateController
     @Mock lateinit var layoutInflater: LayoutInflater
     @Mock lateinit var fingerprintManager: FingerprintManager
     @Mock lateinit var windowManager: WindowManager
@@ -136,6 +138,7 @@
         keyguardBouncerRepository = FakeKeyguardBouncerRepository()
         alternateBouncerInteractor =
             AlternateBouncerInteractor(
+                keyguardStateController,
                 keyguardBouncerRepository,
                 FakeBiometricSettingsRepository(),
                 FakeDeviceEntryFingerprintAuthRepository(),
@@ -169,6 +172,7 @@
         isReverseDefaultRotation: Boolean = false,
         initInfo: DisplayInfo.() -> Unit = {},
         windowInsets: WindowInsets = insetsForSmallNavbar(),
+        inRearDisplayMode: Boolean = false,
         block: () -> Unit
     ) {
         this.deviceConfig = deviceConfig
@@ -229,6 +233,12 @@
             isReverseDefaultRotation
         )
 
+        val rearDisplayDeviceStates = if (inRearDisplayMode) intArrayOf(3) else intArrayOf()
+        sideFpsControllerContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.array.config_rearDisplayDeviceStates,
+            rearDisplayDeviceStates
+        )
+
         sideFpsController =
             SideFpsController(
                 sideFpsControllerContext,
@@ -542,10 +552,62 @@
             { rotation = Surface.ROTATION_270 }
         ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
 
+    @Test
+    fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_0 },
+            inRearDisplayMode = true,
+        ) {
+            verifySfpsIndicator_notAdded_InRearDisplayMode()
+        }
+
+    @Test
+    fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_90 },
+            inRearDisplayMode = true,
+        ) {
+            verifySfpsIndicator_notAdded_InRearDisplayMode()
+        }
+
+    @Test
+    fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_180 },
+            inRearDisplayMode = true,
+        ) {
+            verifySfpsIndicator_notAdded_InRearDisplayMode()
+        }
+
+    @Test
+    fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_270 },
+            inRearDisplayMode = true,
+        ) {
+            verifySfpsIndicator_notAdded_InRearDisplayMode()
+        }
+
     private fun verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible: Boolean) {
         sideFpsController.overlayOffsets = sensorLocation
     }
 
+    private fun verifySfpsIndicator_notAdded_InRearDisplayMode() {
+        sideFpsController.overlayOffsets = sensorLocation
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        verify(windowManager, never()).addView(any(), any())
+    }
+
     fun alternateBouncerVisibility_showAndHideSideFpsUI() = testWithDisplay {
         // WHEN alternate bouncer is visible
         keyguardBouncerRepository.setAlternateVisible(true)
@@ -582,7 +644,7 @@
      * in other rotations have been omitted.
      */
     @Test
-    fun verifiesIndicatorPlacementForXAlignedSensor_0() {
+    fun verifiesIndicatorPlacementForXAlignedSensor_0() =
         testWithDisplay(
             deviceConfig = DeviceConfig.X_ALIGNED,
             isReverseDefaultRotation = false,
@@ -599,7 +661,6 @@
             assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
             assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
         }
-    }
 
     /**
      * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
@@ -608,7 +669,7 @@
      * correctly, tests for indicator placement in other rotations have been omitted.
      */
     @Test
-    fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() {
+    fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
         testWithDisplay(
             deviceConfig = DeviceConfig.X_ALIGNED,
             isReverseDefaultRotation = true,
@@ -625,7 +686,6 @@
             assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
             assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
         }
-    }
 
     /**
      * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
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 54c9d39..86fb279 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
 import kotlinx.coroutines.Dispatchers
@@ -92,6 +93,7 @@
             )
         mAlternateBouncerInteractor =
             AlternateBouncerInteractor(
+                mock(KeyguardStateController::class.java),
                 keyguardBouncerRepository,
                 mock(BiometricSettingsRepository::class.java),
                 mock(DeviceEntryFingerprintAuthRepository::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
index a61cecb..3503902 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
@@ -20,14 +20,14 @@
 
 import static org.mockito.Mockito.mock;
 
-import androidx.test.filters.SmallTest;
-
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
 import android.widget.Button;
 import android.widget.TextView;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -44,18 +44,21 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class BroadcastDialogTest extends SysuiTestCase {
 
-    private static final String SWITCH_APP = "Music";
+    private static final String CURRENT_BROADCAST_APP = "Music";
+    private static final String SWITCH_APP = "Files by Google";
     private static final String TEST_PACKAGE = "com.google.android.apps.nbu.files";
     private BroadcastDialog mBroadcastDialog;
     private View mDialogView;
+    private TextView mTitle;
     private TextView mSubTitle;
+    private Button mSwitchBroadcastAppButton;
     private Button mChangeOutputButton;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mBroadcastDialog = new BroadcastDialog(mContext, mock(MediaOutputDialogFactory.class),
-                SWITCH_APP, TEST_PACKAGE, mock(UiEventLogger.class));
+                CURRENT_BROADCAST_APP, TEST_PACKAGE, mock(UiEventLogger.class));
         mBroadcastDialog.show();
         mDialogView = mBroadcastDialog.mDialogView;
     }
@@ -66,7 +69,15 @@
     }
 
     @Test
-    public void onCreate_withCurrentApp_checkSwitchAppContent() {
+    public void onCreate_withCurrentApp_titleIsCurrentAppName() {
+        mTitle = mDialogView.requireViewById(R.id.dialog_title);
+
+        assertThat(mTitle.getText().toString()).isEqualTo(mContext.getString(
+                R.string.bt_le_audio_broadcast_dialog_title, CURRENT_BROADCAST_APP));
+    }
+
+    @Test
+    public void onCreate_withCurrentApp_subTitleIsSwitchAppName() {
         mSubTitle = mDialogView.requireViewById(R.id.dialog_subtitle);
 
         assertThat(mSubTitle.getText()).isEqualTo(
@@ -74,6 +85,14 @@
     }
 
     @Test
+    public void onCreate_withCurrentApp_switchBtnIsSwitchAppName() {
+        mSwitchBroadcastAppButton = mDialogView.requireViewById(R.id.switch_broadcast);
+
+        assertThat(mSwitchBroadcastAppButton.getText().toString()).isEqualTo(
+                mContext.getString(R.string.bt_le_audio_broadcast_dialog_switch_app, SWITCH_APP));
+    }
+
+    @Test
     public void onClick_withChangeOutput_dismissBroadcastDialog() {
         mChangeOutputButton = mDialogView.requireViewById(R.id.change_output);
         mChangeOutputButton.performClick();
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
index 2ed0346..eafe727 100644
--- 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
@@ -20,6 +20,7 @@
 
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.SeekBar;
 
@@ -42,6 +43,8 @@
 
     private ImageView mIconStart;
     private ImageView mIconEnd;
+    private ViewGroup mIconStartFrame;
+    private ViewGroup mIconEndFrame;
     private SeekBar mSeekbar;
     private SeekBarWithIconButtonsView mIconDiscreteSliderLinearLayout;
 
@@ -50,6 +53,8 @@
         mIconDiscreteSliderLinearLayout = new SeekBarWithIconButtonsView(mContext);
         mIconStart = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start);
         mIconEnd = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end);
+        mIconStartFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start_frame);
+        mIconEndFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end_frame);
         mSeekbar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
     }
 
@@ -59,6 +64,8 @@
 
         assertThat(mIconStart.isEnabled()).isFalse();
         assertThat(mIconEnd.isEnabled()).isTrue();
+        assertThat(mIconStartFrame.isEnabled()).isFalse();
+        assertThat(mIconEndFrame.isEnabled()).isTrue();
     }
 
     @Test
@@ -67,6 +74,8 @@
 
         assertThat(mIconEnd.isEnabled()).isFalse();
         assertThat(mIconStart.isEnabled()).isTrue();
+        assertThat(mIconEndFrame.isEnabled()).isFalse();
+        assertThat(mIconStartFrame.isEnabled()).isTrue();
     }
 
     @Test
@@ -77,12 +86,15 @@
 
         assertThat(mIconStart.isEnabled()).isTrue();
         assertThat(mIconEnd.isEnabled()).isTrue();
+        assertThat(mIconStartFrame.isEnabled()).isTrue();
+        assertThat(mIconEndFrame.isEnabled()).isTrue();
     }
 
     @Test
     public void clickIconEnd_currentProgressIsOneToMax_reachesMax() {
         mIconDiscreteSliderLinearLayout.setProgress(mSeekbar.getMax() - 1);
-        mIconEnd.performClick();
+
+        mIconEndFrame.performClick();
 
         assertThat(mSeekbar.getProgress()).isEqualTo(mSeekbar.getMax());
     }
@@ -90,7 +102,8 @@
     @Test
     public void clickIconStart_currentProgressIsOne_reachesZero() {
         mIconDiscreteSliderLinearLayout.setProgress(1);
-        mIconStart.performClick();
+
+        mIconStartFrame.performClick();
 
         assertThat(mSeekbar.getProgress()).isEqualTo(0);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
deleted file mode 100644
index 34d5661..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.devicepolicy
-
-import android.app.admin.DevicePolicyManager
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
-import androidx.test.filters.SmallTest
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class DevicePolicyManagerExtTest {
-
-    @Mock lateinit var devicePolicyManager: DevicePolicyManager
-    @Mock private lateinit var userTracker: UserTracker
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        whenever(userTracker.userId).thenReturn(CURRENT_USER_ID)
-    }
-
-    // region areKeyguardShortcutsDisabled
-    @Test
-    fun areKeyguardShortcutsDisabled_noDisabledKeyguardFeature_shouldReturnFalse() {
-        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
-            .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE)
-
-        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
-            .isFalse()
-    }
-
-    @Test
-    fun areKeyguardShortcutsDisabled_otherDisabledKeyguardFeatures_shouldReturnFalse() {
-        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
-            .thenReturn(KEYGUARD_DISABLE_SECURE_CAMERA or KEYGUARD_DISABLE_FACE)
-
-        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
-            .isFalse()
-    }
-
-    @Test
-    fun areKeyguardShortcutsDisabled_disabledShortcutsKeyguardFeature_shouldReturnTrue() {
-        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
-            .thenReturn(KEYGUARD_DISABLE_SHORTCUTS_ALL)
-
-        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
-            .isTrue()
-    }
-
-    @Test
-    fun areKeyguardShortcutsDisabled_disabledAllKeyguardFeatures_shouldReturnTrue() {
-        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
-            .thenReturn(KEYGUARD_DISABLE_FEATURES_ALL)
-
-        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
-            .isTrue()
-    }
-    // endregion
-
-    private companion object {
-        const val CURRENT_USER_ID = 123
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 9d4bef6..9e70c27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -231,6 +232,31 @@
         verify(mWindowManager).addView(any(), any());
     }
 
+    // Validates that {@link DreamOverlayService} properly handles the case where the dream's
+    // window is no longer valid by the time start is called.
+    @Test
+    public void testInvalidWindowAddStart() throws Exception {
+        final IDreamOverlayClient client = getClient();
+
+        doThrow(new WindowManager.BadTokenException()).when(mWindowManager).addView(any(), any());
+        // Inform the overlay service of dream starting.
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                false /*shouldShowComplication*/);
+        mMainExecutor.runAllReady();
+
+        verify(mWindowManager).addView(any(), any());
+
+        verify(mStateController).setOverlayActive(false);
+        verify(mStateController).setLowLightActive(false);
+        verify(mStateController).setEntryAnimationsFinished(false);
+
+        verify(mStateController, never()).setOverlayActive(true);
+        verify(mUiEventLogger, never()).log(
+                DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
+
+        verify(mDreamOverlayCallbackController, never()).onStartDream();
+    }
+
     @Test
     public void testDreamOverlayContainerViewControllerInitialized() throws Exception {
         final IDreamOverlayClient client = getClient();
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 18e80ea..1365132 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
@@ -28,6 +28,8 @@
 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.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,6 +52,7 @@
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
     private lateinit var deviceEntryFingerprintAuthRepository:
         FakeDeviceEntryFingerprintAuthRepository
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var systemClock: SystemClock
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var bouncerLogger: TableLogBuffer
@@ -70,6 +73,7 @@
         featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
         underTest =
             AlternateBouncerInteractor(
+                keyguardStateController,
                 bouncerRepository,
                 biometricSettingsRepository,
                 deviceEntryFingerprintAuthRepository,
@@ -134,6 +138,14 @@
     }
 
     @Test
+    fun canShowAlternateBouncerForFingerprint_butCanDismissLockScreen() {
+        givenCanShowAlternateBouncer()
+        whenever(keyguardStateController.isUnlocked).thenReturn(true)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
     fun show_whenCannotShow() {
         givenCannotShowAlternateBouncer()
 
@@ -163,6 +175,7 @@
         biometricSettingsRepository.setStrongBiometricAllowed(true)
         biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
         deviceEntryFingerprintAuthRepository.setLockedOut(false)
+        whenever(keyguardStateController.isUnlocked).thenReturn(false)
     }
 
     private fun givenCannotShowAlternateBouncer() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index a72634b..7f57077 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.controls.ui
 
 import android.app.PendingIntent
+import android.content.res.ColorStateList
 import android.content.res.Configuration
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -26,9 +27,9 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -49,7 +50,7 @@
 import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
 import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
@@ -89,7 +90,6 @@
     @Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
     @Mock lateinit var mediaHostState: MediaHostState
     @Mock lateinit var activityStarter: ActivityStarter
-    @Mock @Main private lateinit var executor: DelayableExecutor
     @Mock lateinit var mediaDataManager: MediaDataManager
     @Mock lateinit var configurationController: ConfigurationController
     @Mock lateinit var falsingCollector: FalsingCollector
@@ -113,11 +113,15 @@
 
     private val clock = FakeSystemClock()
     private lateinit var mediaCarouselController: MediaCarouselController
+    private lateinit var mainExecutor: FakeExecutor
+    private lateinit var backgroundExecutor: FakeExecutor
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
         transitionRepository = FakeKeyguardTransitionRepository()
+        mainExecutor = FakeExecutor(clock)
+        backgroundExecutor = FakeExecutor(clock)
         mediaCarouselController =
             MediaCarouselController(
                 context,
@@ -126,7 +130,8 @@
                 mediaHostStatesManager,
                 activityStarter,
                 clock,
-                executor,
+                mainExecutor,
+                backgroundExecutor,
                 mediaDataManager,
                 configurationController,
                 falsingCollector,
@@ -401,6 +406,7 @@
                 resumption = true
             )
         )
+        runAllReady()
 
         assertEquals(
             MediaPlayerData.getMediaPlayerIndex("paused local"),
@@ -510,6 +516,8 @@
             false
         )
         mediaCarouselController.shouldScrollToKey = true
+        runAllReady()
+
         // switching between media players.
         listener.value.onMediaDataLoaded(
             "playing local",
@@ -531,6 +539,7 @@
                 resumption = false
             )
         )
+        runAllReady()
 
         assertEquals(
             MediaPlayerData.getMediaPlayerIndex("paused local"),
@@ -555,6 +564,7 @@
                 resumption = false
             )
         )
+        runAllReady()
 
         var playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
         assertEquals(
@@ -577,6 +587,8 @@
                 packageName = "PACKAGE_NAME"
             )
         )
+        runAllReady()
+
         playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
         assertEquals(playerIndex, 0)
     }
@@ -674,6 +686,8 @@
 
     @Test
     fun testOnConfigChanged_playersAreAddedBack() {
+        mediaCarouselController.pageIndicator = pageIndicator
+
         listener.value.onMediaDataLoaded(
             "playing local",
             null,
@@ -694,11 +708,15 @@
                 resumption = false
             )
         )
+        runAllReady()
 
         val playersSize = MediaPlayerData.players().size
 
         configListener.value.onConfigChanged(Configuration())
+        runAllReady()
 
+        verify(pageIndicator).tintList =
+            ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
         assertEquals(playersSize, MediaPlayerData.players().size)
         assertEquals(
             MediaPlayerData.getMediaPlayerIndex("playing local"),
@@ -707,6 +725,93 @@
     }
 
     @Test
+    fun testOnUiModeChanged_playersAreAddedBack() {
+        mediaCarouselController.pageIndicator = pageIndicator
+
+        listener.value.onMediaDataLoaded(
+            "paused local",
+            null,
+            DATA.copy(
+                active = true,
+                isPlaying = false,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = false
+            )
+        )
+        runAllReady()
+
+        val playersSize = MediaPlayerData.players().size
+        configListener.value.onUiModeChanged()
+        runAllReady()
+
+        verify(pageIndicator).tintList =
+            ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
+        assertEquals(playersSize, MediaPlayerData.players().size)
+        assertEquals(
+            MediaPlayerData.getMediaPlayerIndex("paused local"),
+            mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+        )
+    }
+
+    @Test
+    fun testOnDensityOrFontScaleChanged_playersAreAddedBack() {
+        mediaCarouselController.pageIndicator = pageIndicator
+
+        listener.value.onMediaDataLoaded(
+            "paused local",
+            null,
+            DATA.copy(
+                active = true,
+                isPlaying = false,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = false
+            )
+        )
+        runAllReady()
+
+        val playersSize = MediaPlayerData.players().size
+        configListener.value.onDensityOrFontScaleChanged()
+        runAllReady()
+
+        verify(pageIndicator).tintList =
+            ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
+        assertEquals(playersSize, MediaPlayerData.players().size)
+        assertEquals(
+            MediaPlayerData.getMediaPlayerIndex("paused local"),
+            mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+        )
+    }
+
+    @Test
+    fun testOnThemeChanged_playersAreAddedBack() {
+        mediaCarouselController.pageIndicator = pageIndicator
+
+        listener.value.onMediaDataLoaded(
+            "paused local",
+            null,
+            DATA.copy(
+                active = true,
+                isPlaying = false,
+                playbackLocation = MediaData.PLAYBACK_LOCAL,
+                resumption = false
+            )
+        )
+        runAllReady()
+
+        val playersSize = MediaPlayerData.players().size
+        configListener.value.onThemeChanged()
+        runAllReady()
+
+        verify(pageIndicator).tintList =
+            ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
+        assertEquals(playersSize, MediaPlayerData.players().size)
+        assertEquals(
+            MediaPlayerData.getMediaPlayerIndex("paused local"),
+            mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+        )
+    }
+
+    @Test
     fun testRecommendation_persistentEnabled_newSmartspaceLoaded_updatesSort() {
         testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded()
 
@@ -832,4 +937,9 @@
         // Verify that seekbar listening attribute in media control panel is set to false.
         verify(panel, times(MediaPlayerData.players().size)).listening = false
     }
+
+    private fun runAllReady() {
+        backgroundExecutor.runAllReady()
+        mainExecutor.runAllReady()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 52b29ac..39c4e06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
-import android.app.admin.DevicePolicyManager
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
@@ -30,7 +29,6 @@
 import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
 import com.android.systemui.notetask.NoteTaskInfoResolver.NoteTaskInfo
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
@@ -41,7 +39,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
@@ -67,8 +64,6 @@
     @Mock lateinit var optionalUserManager: Optional<UserManager>
     @Mock lateinit var userManager: UserManager
     @Mock lateinit var uiEventLogger: UiEventLogger
-    @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
 
     @Before
     fun setUp() {
@@ -80,13 +75,6 @@
         whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
         whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
         whenever(userManager.isUserUnlocked).thenReturn(true)
-        whenever(
-                devicePolicyManager.getKeyguardDisabledFeatures(
-                    /* admin= */ eq(null),
-                    /* userHandle= */ anyInt()
-                )
-            )
-            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE)
     }
 
     private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
@@ -96,10 +84,8 @@
             optionalBubbles = optionalBubbles,
             optionalKeyguardManager = optionalKeyguardManager,
             optionalUserManager = optionalUserManager,
-            devicePolicyManager = devicePolicyManager,
             isEnabled = isEnabled,
             uiEventLogger = uiEventLogger,
-            userTracker = userTracker,
         )
     }
 
@@ -305,86 +291,6 @@
     }
     // endregion
 
-    // region keyguard policy
-    @Test
-    fun showNoteTask_keyguardLocked_keyguardDisableShortcutsAll_shouldDoNothing() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
-        whenever(
-                devicePolicyManager.getKeyguardDisabledFeatures(
-                    /* admin= */ eq(null),
-                    /* userHandle= */ anyInt()
-                )
-            )
-            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
-
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
-
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
-    }
-
-    @Test
-    fun showNoteTask_keyguardLocked_keyguardDisableFeaturesAll_shouldDoNothing() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
-        whenever(
-                devicePolicyManager.getKeyguardDisabledFeatures(
-                    /* admin= */ eq(null),
-                    /* userHandle= */ anyInt()
-                )
-            )
-            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
-
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
-
-        verifyZeroInteractions(context, bubbles, uiEventLogger)
-    }
-
-    @Test
-    fun showNoteTask_keyguardUnlocked_keyguardDisableShortcutsAll_shouldStartBubble() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
-        whenever(
-                devicePolicyManager.getKeyguardDisabledFeatures(
-                    /* admin= */ eq(null),
-                    /* userHandle= */ anyInt()
-                )
-            )
-            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
-
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
-
-        val intentCaptor = argumentCaptor<Intent>()
-        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
-        intentCaptor.value.let { intent ->
-            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
-            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
-            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
-            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
-        }
-    }
-
-    @Test
-    fun showNoteTask_keyguardUnlocked_keyguardDisableFeaturesAll_shouldStartBubble() {
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
-        whenever(
-                devicePolicyManager.getKeyguardDisabledFeatures(
-                    /* admin= */ eq(null),
-                    /* userHandle= */ anyInt()
-                )
-            )
-            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
-
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
-
-        val intentCaptor = argumentCaptor<Intent>()
-        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
-        intentCaptor.value.let { intent ->
-            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
-            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
-            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
-            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
-        }
-    }
-    // endregion
-
     private companion object {
         const val NOTES_PACKAGE_NAME = "com.android.note.app"
         const val NOTES_UID = 123456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 5058373..3d55c51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -71,7 +71,7 @@
     @Mock
     private QSPanel mQSPanel;
     @Mock
-    private QSTileHost mQSTileHost;
+    private QSHost mQSHost;
     @Mock
     private QSCustomizerController mQSCustomizerController;
     @Mock
@@ -105,7 +105,7 @@
 
     /** Implementation needed to ensure we have a reflectively-available class name. */
     private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
-        protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
+        protected TestableQSPanelControllerBase(QSPanel view, QSHost host,
                 QSCustomizerController qsCustomizerController, MediaHost mediaHost,
                 MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
                 DumpManager dumpManager) {
@@ -130,8 +130,8 @@
         when(mQSPanel.getOrCreateTileLayout()).thenReturn(mPagedTileLayout);
         when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout);
         when(mQSTile.getTileSpec()).thenReturn("dnd");
-        when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
-        when(mQSTileHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
+        when(mQSHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
+        when(mQSHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
         when(mQSTileRevealControllerFactory.create(any(), any()))
                 .thenReturn(mQSTileRevealController);
         when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
@@ -142,7 +142,7 @@
             return null;
         }).when(mQSPanel).setListening(anyBoolean());
 
-        mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
+        mController = new TestableQSPanelControllerBase(mQSPanel, mQSHost,
                 mQSCustomizerController, mMediaHost,
                 mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
 
@@ -155,7 +155,7 @@
         mController.onViewDetached();
 
         QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
-                mQSTileHost, mQSCustomizerController, mMediaHost,
+                mQSHost, mQSCustomizerController, mMediaHost,
                 mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
             @Override
             protected QSTileRevealController createTileRevealController() {
@@ -250,7 +250,7 @@
 
         when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
         when(mQSPanel.getDumpableTag()).thenReturn("QSPanelLandscape");
-        mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
+        mController = new TestableQSPanelControllerBase(mQSPanel, mQSHost,
                 mQSCustomizerController, mMediaHost,
                 mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
         mController.init();
@@ -259,7 +259,7 @@
 
         when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
         when(mQSPanel.getDumpableTag()).thenReturn("QSPanelPortrait");
-        mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
+        mController = new TestableQSPanelControllerBase(mQSPanel, mQSHost,
                 mQSCustomizerController, mMediaHost,
                 mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
         mController.init();
@@ -291,7 +291,7 @@
 
     @Test
     public void testRefreshAllTilesDoesntRefreshListeningTiles() {
-        when(mQSTileHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
+        when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
         mController.setTiles();
 
         when(mQSTile.isListening()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 5c5fbc9..a0d8f98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -40,7 +40,7 @@
 
     @Mock private lateinit var qsPanel: QSPanel
     @Mock private lateinit var tunerService: TunerService
-    @Mock private lateinit var qsTileHost: QSTileHost
+    @Mock private lateinit var qsHost: QSHost
     @Mock private lateinit var qsCustomizerController: QSCustomizerController
     @Mock private lateinit var qsTileRevealControllerFactory: QSTileRevealController.Factory
     @Mock private lateinit var dumpManager: DumpManager
@@ -79,7 +79,7 @@
         controller = QSPanelController(
             qsPanel,
             tunerService,
-            qsTileHost,
+            qsHost,
             qsCustomizerController,
             /* usingMediaPlayer= */ true,
             mediaHost,
@@ -109,7 +109,7 @@
 
     @Test
     fun testSetListeningDoesntRefreshListeningTiles() {
-        whenever(qsTileHost.getTiles()).thenReturn(listOf(tile, otherTile))
+        whenever(qsHost.getTiles()).thenReturn(listOf(tile, otherTile))
         controller.setTiles()
         whenever(tile.isListening()).thenReturn(false)
         whenever(otherTile.isListening()).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index fb1a720..34d2b14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -69,7 +69,6 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.FakeSharedPreferences;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -100,11 +99,9 @@
     private static ComponentName CUSTOM_TILE =
             ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS");
     private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE);
-    private static final String SETTING = QSTileHost.TILES_SETTING;
+    private static final String SETTING = QSHost.TILES_SETTING;
 
     @Mock
-    private StatusBarIconController mIconController;
-    @Mock
     private QSFactory mDefaultFactory;
     @Mock
     private PluginManager mPluginManager;
@@ -167,7 +164,7 @@
 
         mSecureSettings = new FakeSettings();
         saveSetting("");
-        mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mMainExecutor,
+        mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
                 mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
                 mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
                 mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory,
@@ -248,44 +245,44 @@
     public void testRemoveWifiAndCellularWithoutInternet() {
         saveSetting("wifi, spec1, cell, spec2");
 
-        assertEquals("internet", mQSTileHost.mTileSpecs.get(0));
-        assertEquals("spec1", mQSTileHost.mTileSpecs.get(1));
-        assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+        assertEquals("internet", mQSTileHost.getSpecs().get(0));
+        assertEquals("spec1", mQSTileHost.getSpecs().get(1));
+        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
     }
 
     @Test
     public void testRemoveWifiAndCellularWithInternet() {
         saveSetting("wifi, spec1, cell, spec2, internet");
 
-        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
-        assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
-        assertEquals("internet", mQSTileHost.mTileSpecs.get(2));
+        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
+        assertEquals("internet", mQSTileHost.getSpecs().get(2));
     }
 
     @Test
     public void testRemoveWifiWithoutInternet() {
         saveSetting("spec1, wifi, spec2");
 
-        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
-        assertEquals("internet", mQSTileHost.mTileSpecs.get(1));
-        assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+        assertEquals("internet", mQSTileHost.getSpecs().get(1));
+        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
     }
 
     @Test
     public void testRemoveCellWithInternet() {
         saveSetting("spec1, spec2, cell, internet");
 
-        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
-        assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
-        assertEquals("internet", mQSTileHost.mTileSpecs.get(2));
+        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
+        assertEquals("internet", mQSTileHost.getSpecs().get(2));
     }
 
     @Test
     public void testNoWifiNoCellularNoInternet() {
         saveSetting("spec1,spec2");
 
-        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
-        assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
     }
 
     @Test
@@ -332,9 +329,9 @@
 
         mQSTileHost.addTile("spec1");
 
-        assertEquals(2, mQSTileHost.mTileSpecs.size());
-        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
-        assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+        assertEquals(2, mQSTileHost.getSpecs().size());
+        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
     }
 
     @Test
@@ -346,10 +343,10 @@
         mQSTileHost.addTile("spec2", 1);
         mMainExecutor.runAllReady();
 
-        assertEquals(3, mQSTileHost.mTileSpecs.size());
-        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
-        assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
-        assertEquals("spec3", mQSTileHost.mTileSpecs.get(2));
+        assertEquals(3, mQSTileHost.getSpecs().size());
+        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
+        assertEquals("spec3", mQSTileHost.getSpecs().get(2));
     }
 
     @Test
@@ -361,10 +358,10 @@
         mQSTileHost.addTile("spec2", 100);
         mMainExecutor.runAllReady();
 
-        assertEquals(3, mQSTileHost.mTileSpecs.size());
-        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
-        assertEquals("spec3", mQSTileHost.mTileSpecs.get(1));
-        assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+        assertEquals(3, mQSTileHost.getSpecs().size());
+        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+        assertEquals("spec3", mQSTileHost.getSpecs().get(1));
+        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
     }
 
     @Test
@@ -376,10 +373,10 @@
         mQSTileHost.addTile("spec2", QSTileHost.POSITION_AT_END);
         mMainExecutor.runAllReady();
 
-        assertEquals(3, mQSTileHost.mTileSpecs.size());
-        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
-        assertEquals("spec3", mQSTileHost.mTileSpecs.get(1));
-        assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+        assertEquals(3, mQSTileHost.getSpecs().size());
+        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+        assertEquals("spec3", mQSTileHost.getSpecs().get(1));
+        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
     }
 
     @Test
@@ -389,8 +386,8 @@
         mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
         mMainExecutor.runAllReady();
 
-        assertEquals(1, mQSTileHost.mTileSpecs.size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
+        assertEquals(1, mQSTileHost.getSpecs().size());
+        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
     }
 
     @Test
@@ -400,8 +397,8 @@
         mQSTileHost.addTile(CUSTOM_TILE);
         mMainExecutor.runAllReady();
 
-        assertEquals(2, mQSTileHost.mTileSpecs.size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
+        assertEquals(2, mQSTileHost.getSpecs().size());
+        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
     }
 
     @Test
@@ -411,8 +408,8 @@
         mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
         mMainExecutor.runAllReady();
 
-        assertEquals(2, mQSTileHost.mTileSpecs.size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
+        assertEquals(2, mQSTileHost.getSpecs().size());
+        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
     }
 
     @Test
@@ -422,8 +419,8 @@
         mQSTileHost.addTile(CUSTOM_TILE, /* end */ true);
         mMainExecutor.runAllReady();
 
-        assertEquals(2, mQSTileHost.mTileSpecs.size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(1));
+        assertEquals(2, mQSTileHost.getSpecs().size());
+        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(1));
     }
 
     @Test
@@ -478,7 +475,7 @@
         mQSTileHost.removeTiles(List.of("spec1", "spec2"));
 
         mMainExecutor.runAllReady();
-        assertEquals(List.of("spec3"), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec3"), mQSTileHost.getSpecs());
     }
 
     @Test
@@ -488,7 +485,7 @@
         mQSTileHost.removeTile("spec3");
 
         mMainExecutor.runAllReady();
-        assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
         assertEquals("spec2", getSetting());
     }
 
@@ -497,10 +494,10 @@
         saveSetting("spec1,spec2");
 
         mQSTileHost.addTile("spec3");
-        assertEquals(List.of("spec1", "spec2"), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs());
 
         mMainExecutor.runAllReady();
-        assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs());
     }
 
     @Test
@@ -508,10 +505,10 @@
         saveSetting("spec1,spec2");
 
         mQSTileHost.removeTile("spec1");
-        assertEquals(List.of("spec1", "spec2"), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs());
 
         mMainExecutor.runAllReady();
-        assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
     }
 
     @Test
@@ -519,10 +516,10 @@
         saveSetting("spec1,spec2,spec3");
 
         mQSTileHost.removeTiles(List.of("spec3", "spec1"));
-        assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs());
 
         mMainExecutor.runAllReady();
-        assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
     }
 
     @Test
@@ -530,17 +527,17 @@
         saveSetting("spec1," + CUSTOM_TILE_SPEC);
 
         mQSTileHost.removeTileByUser(CUSTOM_TILE);
-        assertEquals(List.of("spec1", CUSTOM_TILE_SPEC), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec1", CUSTOM_TILE_SPEC), mQSTileHost.getSpecs());
 
         mMainExecutor.runAllReady();
-        assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
     }
 
     @Test
     public void testNonValidTileNotStoredInSettings() {
         saveSetting("spec1,not-valid");
 
-        assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
         assertEquals("spec1", getSetting());
     }
 
@@ -548,14 +545,14 @@
     public void testNotAvailableTileNotStoredInSettings() {
         saveSetting("spec1,na");
 
-        assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+        assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
         assertEquals("spec1", getSetting());
     }
 
     @Test
     public void testIsTileAdded_true() {
         int user = mUserTracker.getUserId();
-        getSharedPreferenecesForUser(user)
+        getSharedPreferencesForUser(user)
                 .edit()
                 .putBoolean(CUSTOM_TILE.flattenToString(), true)
                 .apply();
@@ -566,7 +563,7 @@
     @Test
     public void testIsTileAdded_false() {
         int user = mUserTracker.getUserId();
-        getSharedPreferenecesForUser(user)
+        getSharedPreferencesForUser(user)
                 .edit()
                 .putBoolean(CUSTOM_TILE.flattenToString(), false)
                 .apply();
@@ -597,7 +594,7 @@
         int user = mUserTracker.getUserId();
         mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
 
-        assertTrue(getSharedPreferenecesForUser(user)
+        assertTrue(getSharedPreferencesForUser(user)
                 .getBoolean(CUSTOM_TILE.flattenToString(), false));
     }
 
@@ -606,7 +603,7 @@
         int user = mUserTracker.getUserId();
         mQSTileHost.setTileAdded(CUSTOM_TILE, user, false);
 
-        assertFalse(getSharedPreferenecesForUser(user)
+        assertFalse(getSharedPreferencesForUser(user)
                 .getBoolean(CUSTOM_TILE.flattenToString(), false));
     }
 
@@ -615,7 +612,7 @@
         int user = mUserTracker.getUserId();
         mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
 
-        assertFalse(getSharedPreferenecesForUser(user + 1)
+        assertFalse(getSharedPreferencesForUser(user + 1)
                 .getBoolean(CUSTOM_TILE.flattenToString(), false));
     }
 
@@ -627,8 +624,8 @@
         // This will be done by TileServiceManager
         mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
 
-        mQSTileHost.changeTilesByUser(mQSTileHost.mTileSpecs, List.of("spec1"));
-        assertFalse(getSharedPreferenecesForUser(user)
+        mQSTileHost.changeTilesByUser(mQSTileHost.getSpecs(), List.of("spec1"));
+        assertFalse(getSharedPreferencesForUser(user)
                 .getBoolean(CUSTOM_TILE.flattenToString(), false));
     }
 
@@ -642,7 +639,7 @@
 
         mQSTileHost.removeTileByUser(CUSTOM_TILE);
         mMainExecutor.runAllReady();
-        assertFalse(getSharedPreferenecesForUser(user)
+        assertFalse(getSharedPreferencesForUser(user)
                 .getBoolean(CUSTOM_TILE.flattenToString(), false));
     }
 
@@ -656,7 +653,7 @@
 
         mQSTileHost.removeTile(CUSTOM_TILE_SPEC);
         mMainExecutor.runAllReady();
-        assertFalse(getSharedPreferenecesForUser(user)
+        assertFalse(getSharedPreferencesForUser(user)
                 .getBoolean(CUSTOM_TILE.flattenToString(), false));
     }
 
@@ -681,12 +678,12 @@
         assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
     }
 
-    private SharedPreferences getSharedPreferenecesForUser(int user) {
+    private SharedPreferences getSharedPreferencesForUser(int user) {
         return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
     }
 
     private class TestQSTileHost extends QSTileHost {
-        TestQSTileHost(Context context, StatusBarIconController iconController,
+        TestQSTileHost(Context context,
                 QSFactory defaultFactory, Executor mainExecutor,
                 PluginManager pluginManager, TunerService tunerService,
                 Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
@@ -696,7 +693,7 @@
                 TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
                 TileLifecycleManager.Factory tileLifecycleManagerFactory,
                 UserFileManager userFileManager) {
-            super(context, iconController, defaultFactory, mainExecutor, pluginManager,
+            super(context, defaultFactory, mainExecutor, pluginManager,
                     tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
                     uiEventLogger, userTracker, secureSettings, customTileStatePersister,
                     tileServiceRequestControllerBuilder, tileLifecycleManagerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index f53e997..71ea831 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -50,7 +50,7 @@
 class QuickQSPanelControllerTest : SysuiTestCase() {
 
     @Mock private lateinit var quickQSPanel: QuickQSPanel
-    @Mock private lateinit var qsTileHost: QSTileHost
+    @Mock private lateinit var qsHost: QSHost
     @Mock private lateinit var qsCustomizerController: QSCustomizerController
     @Mock private lateinit var mediaHost: MediaHost
     @Mock private lateinit var metricsLogger: MetricsLogger
@@ -75,12 +75,12 @@
         whenever(quickQSPanel.isAttachedToWindow).thenReturn(true)
         whenever(quickQSPanel.dumpableTag).thenReturn("")
         whenever(quickQSPanel.resources).thenReturn(mContext.resources)
-        whenever(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
+        whenever(qsHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
 
         controller =
             TestQuickQSPanelController(
                 quickQSPanel,
-                qsTileHost,
+                qsHost,
                 qsCustomizerController,
                 /* usingMediaPlayer = */ false,
                 mediaHost,
@@ -102,7 +102,7 @@
     fun testTileSublistWithFewerTiles_noCrash() {
         whenever(quickQSPanel.numQuickTiles).thenReturn(3)
 
-        whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile))
+        whenever(qsHost.tiles).thenReturn(listOf(tile, tile))
 
         controller.setTiles()
     }
@@ -111,7 +111,7 @@
     fun testTileSublistWithTooManyTiles() {
         val limit = 3
         whenever(quickQSPanel.numQuickTiles).thenReturn(limit)
-        whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
+        whenever(qsHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
 
         controller.setTiles()
 
@@ -147,7 +147,7 @@
 
     class TestQuickQSPanelController(
         view: QuickQSPanel,
-        qsTileHost: QSTileHost,
+        qsHost: QSHost,
         qsCustomizerController: QSCustomizerController,
         usingMediaPlayer: Boolean,
         mediaHost: MediaHost,
@@ -159,7 +159,7 @@
     ) :
         QuickQSPanelController(
             view,
-            qsTileHost,
+            qsHost,
             qsCustomizerController,
             usingMediaPlayer,
             mediaHost,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
index d42cbe3..c041cb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
@@ -25,7 +25,7 @@
 
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -42,19 +42,19 @@
 
     private TileAdapter mTileAdapter;
     @Mock
-    private QSTileHost mQSTileHost;
+    private QSHost mQSHost;
 
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         TestableLooper.get(this).runWithLooper(() -> mTileAdapter =
-                new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake()));
+                new TileAdapter(mContext, mQSHost, new UiEventLoggerFake()));
     }
 
     @Test
     public void testResetNotifiesHost() {
         mTileAdapter.resetTileSpecs(Collections.emptyList());
-        verify(mQSTileHost).changeTilesByUser(any(), any());
+        verify(mQSHost).changeTilesByUser(any(), any());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 040af70..78a0258 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -55,7 +55,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -102,7 +102,7 @@
     @Mock
     private TileQueryHelper.TileStateListener mListener;
     @Mock
-    private QSTileHost mQSTileHost;
+    private QSHost mQSHost;
     @Mock
     private PackageManager mPackageManager;
     @Mock
@@ -131,7 +131,7 @@
                         return null;
                     }
                 }
-        ).when(mQSTileHost).createTile(anyString());
+        ).when(mQSHost).createTile(anyString());
         FakeSystemClock clock = new FakeSystemClock();
         mMainExecutor = new FakeExecutor(clock);
         mBgExecutor = new FakeExecutor(clock);
@@ -147,7 +147,7 @@
 
     @Test
     public void testIsFinished_trueAfterQuerying() {
-        mTileQueryHelper.queryTiles(mQSTileHost);
+        mTileQueryHelper.queryTiles(mQSHost);
 
         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
 
@@ -156,7 +156,7 @@
 
     @Test
     public void testQueryTiles_callsListenerTwice() {
-        mTileQueryHelper.queryTiles(mQSTileHost);
+        mTileQueryHelper.queryTiles(mQSHost);
 
         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
 
@@ -170,7 +170,7 @@
             return null;
         }).when(mListener).onTilesChanged(any());
 
-        mTileQueryHelper.queryTiles(mQSTileHost);
+        mTileQueryHelper.queryTiles(mQSHost);
 
         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
 
@@ -184,7 +184,7 @@
         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
                 STOCK_TILES);
 
-        mTileQueryHelper.queryTiles(mQSTileHost);
+        mTileQueryHelper.queryTiles(mQSHost);
 
         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
 
@@ -204,7 +204,7 @@
         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
                 STOCK_TILES);
 
-        mTileQueryHelper.queryTiles(mQSTileHost);
+        mTileQueryHelper.queryTiles(mQSHost);
 
         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
 
@@ -224,7 +224,7 @@
         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
                 STOCK_TILES);
 
-        mTileQueryHelper.queryTiles(mQSTileHost);
+        mTileQueryHelper.queryTiles(mQSHost);
 
         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
 
@@ -240,9 +240,9 @@
     public void testCustomTileNotCreated() {
         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES,
                 CUSTOM_TILE);
-        mTileQueryHelper.queryTiles(mQSTileHost);
+        mTileQueryHelper.queryTiles(mQSHost);
         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
-        verify(mQSTileHost, never()).createTile(CUSTOM_TILE);
+        verify(mQSHost, never()).createTile(CUSTOM_TILE);
     }
 
     @Test
@@ -264,7 +264,7 @@
         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
                 "");
 
-        mTileQueryHelper.queryTiles(mQSTileHost);
+        mTileQueryHelper.queryTiles(mQSHost);
         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
 
         verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture());
@@ -278,7 +278,7 @@
         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null);
         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
                 STOCK_TILES);
-        mTileQueryHelper.queryTiles(mQSTileHost);
+        mTileQueryHelper.queryTiles(mQSHost);
     }
 
     @Test
@@ -286,12 +286,12 @@
         Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null);
 
         QSTile t = mock(QSTile.class);
-        when(mQSTileHost.createTile("hotspot")).thenReturn(t);
+        when(mQSHost.createTile("hotspot")).thenReturn(t);
 
         mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
                 "hotspot");
 
-        mTileQueryHelper.queryTiles(mQSTileHost);
+        mTileQueryHelper.queryTiles(mQSHost);
 
         FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
         InOrder verifier = inOrder(t);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 8aa625a..46af89e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -39,7 +39,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.settings.UserTracker;
 
 import org.junit.After;
@@ -61,7 +61,7 @@
     @Mock
     private UserTracker mUserTracker;
     @Mock
-    private QSTileHost mQSTileHost;
+    private QSHost mQSHost;
     @Mock
     private Context mMockContext;
 
@@ -80,7 +80,7 @@
         when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
 
         when(mTileServices.getContext()).thenReturn(mMockContext);
-        when(mTileServices.getHost()).thenReturn(mQSTileHost);
+        when(mTileServices.getHost()).thenReturn(mQSHost);
         when(mTileLifecycle.getUserId()).thenAnswer(invocation -> mUserTracker.getUserId());
         when(mTileLifecycle.isActiveTile()).thenReturn(false);
 
@@ -98,28 +98,28 @@
 
     @Test
     public void testSetTileAddedIfNotAdded() {
-        when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+        when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
         mTileServiceManager.startLifecycleManagerAndAddTile();
 
-        verify(mQSTileHost).setTileAdded(mComponentName, mUserTracker.getUserId(), true);
+        verify(mQSHost).setTileAdded(mComponentName, mUserTracker.getUserId(), true);
     }
 
     @Test
     public void testNotSetTileAddedIfAdded() {
-        when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(true);
+        when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(true);
         mTileServiceManager.startLifecycleManagerAndAddTile();
 
-        verify(mQSTileHost, never()).setTileAdded(eq(mComponentName), anyInt(), eq(true));
+        verify(mQSHost, never()).setTileAdded(eq(mComponentName), anyInt(), eq(true));
     }
 
     @Test
     public void testSetTileAddedCorrectUser() {
         int user = 10;
         when(mUserTracker.getUserId()).thenReturn(user);
-        when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+        when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
         mTileServiceManager.startLifecycleManagerAndAddTile();
 
-        verify(mQSTileHost).setTileAdded(mComponentName, user, true);
+        verify(mQSHost).setTileAdded(mComponentName, user, true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
index bdfbca4..ccfb5cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
@@ -27,27 +27,27 @@
 import com.android.internal.statusbar.IAddTileResultCallback
 import com.android.systemui.InstanceIdSequenceFake
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.util.function.Consumer
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -62,7 +62,7 @@
     @Mock
     private lateinit var tileRequestDialog: TileRequestDialog
     @Mock
-    private lateinit var qsTileHost: QSTileHost
+    private lateinit var qsHost: QSHost
     @Mock
     private lateinit var commandRegistry: CommandRegistry
     @Mock
@@ -82,10 +82,10 @@
         `when`(logger.newInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
 
         // Tile not present by default
-        `when`(qsTileHost.indexOf(anyString())).thenReturn(-1)
+        `when`(qsHost.indexOf(anyString())).thenReturn(-1)
 
         controller = TileServiceRequestController(
-                qsTileHost,
+                qsHost,
                 commandQueue,
                 commandRegistry,
                 logger
@@ -107,18 +107,18 @@
 
     @Test
     fun tileAlreadyAdded_correctResult() {
-        `when`(qsTileHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
+        `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
 
         val callback = Callback()
         controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
 
         assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
-        verify(qsTileHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
+        verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
     }
 
     @Test
     fun tileAlreadyAdded_logged() {
-        `when`(qsTileHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
+        `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
 
         controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
 
@@ -157,7 +157,7 @@
 
         cancelListenerCaptor.value.onCancel(tileRequestDialog)
         assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
-        verify(qsTileHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
+        verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
     }
 
     @Test
@@ -191,7 +191,7 @@
         clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
 
         assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE)
-        verify(qsTileHost).addTile(TEST_COMPONENT, /* end */ true)
+        verify(qsHost).addTile(TEST_COMPONENT, /* end */ true)
     }
 
     @Test
@@ -225,7 +225,7 @@
         clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
 
         assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DONT_ADD_TILE)
-        verify(qsTileHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
+        verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
     }
 
     @Test
@@ -266,7 +266,7 @@
 
     @Test
     fun commandQueueCallback_callbackCalled() {
-        `when`(qsTileHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
+        `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
         val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
         verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
         val c = Callback()
@@ -365,4 +365,4 @@
             accept(r)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 172c87f..64e9a3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -30,7 +30,6 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.quicksettings.IQSTileService;
@@ -39,24 +38,13 @@
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.qs.QSTileHost;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSFactoryImpl;
-import com.android.systemui.settings.UserFileManager;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -68,8 +56,6 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.Optional;
-import java.util.concurrent.Executor;
 
 import javax.inject.Provider;
 
@@ -92,26 +78,8 @@
     @Mock
     private StatusBarIconController mStatusBarIconController;
     @Mock
-    private QSFactoryImpl mQSFactory;
-    @Mock
-    private PluginManager mPluginManager;
-    @Mock
-    private  TunerService mTunerService;
-    @Mock
-    private AutoTileManager mAutoTileManager;
-    @Mock
-    private DumpManager mDumpManager;
-    @Mock
-    private CentralSurfaces mCentralSurfaces;
-    @Mock
-    private QSLogger mQSLogger;
-    @Mock
-    private UiEventLogger mUiEventLogger;
-    @Mock
     private UserTracker mUserTracker;
     @Mock
-    private SecureSettings  mSecureSettings;
-    @Mock
     private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder;
     @Mock
     private TileServiceRequestController mTileServiceRequestController;
@@ -122,12 +90,11 @@
     @Mock
     private TileLifecycleManager mTileLifecycleManager;
     @Mock
-    private UserFileManager mUserFileManager;
+    private QSHost mQSHost;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(BluetoothController.class);
         mManagers = new ArrayList<>();
         mTestableLooper = TestableLooper.get(this);
 
@@ -135,34 +102,16 @@
                 .thenReturn(mTileServiceRequestController);
         when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
                 .thenReturn(mTileLifecycleManager);
+        when(mQSHost.getContext()).thenReturn(mContext);
 
         Provider<Handler> provider = () -> new Handler(mTestableLooper.getLooper());
-        Executor executor = new HandlerExecutor(provider.get());
 
-        QSTileHost host = new QSTileHost(mContext,
-                mStatusBarIconController,
-                mQSFactory,
-                executor,
-                mPluginManager,
-                mTunerService,
-                () -> mAutoTileManager,
-                mDumpManager,
-                Optional.of(mCentralSurfaces),
-                mQSLogger,
-                mUiEventLogger,
-                mUserTracker,
-                mSecureSettings,
-                mock(CustomTileStatePersister.class),
-                mTileServiceRequestControllerBuilder,
-                mTileLifecycleManagerFactory,
-                mUserFileManager);
-        mTileService = new TestTileServices(host, provider, mBroadcastDispatcher,
-                mUserTracker, mKeyguardStateController, mCommandQueue);
+        mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher,
+                mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController);
     }
 
     @After
     public void tearDown() throws Exception {
-        mTileService.getHost().destroy();
         mTileService.destroy();
         TestableLooper.get(this).processAllMessages();
     }
@@ -274,11 +223,12 @@
     }
 
     private class TestTileServices extends TileServices {
-        TestTileServices(QSTileHost host, Provider<Handler> handlerProvider,
+        TestTileServices(QSHost host, Provider<Handler> handlerProvider,
                 BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
-                KeyguardStateController keyguardStateController, CommandQueue commandQueue) {
+                KeyguardStateController keyguardStateController, CommandQueue commandQueue,
+                StatusBarIconController statusBarIconController) {
             super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
-                    commandQueue);
+                    commandQueue, statusBarIconController);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index ba49f3f..36549fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -69,7 +69,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSEvent;
 import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.StatusBarState;
 
@@ -97,7 +96,7 @@
     private TestableLooper mTestableLooper;
     private TileImpl mTile;
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private MetricsLogger mMetricsLogger;
     private final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index d65901777..bf172f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -17,7 +17,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.BluetoothController
@@ -40,7 +40,7 @@
     @Mock
     private lateinit var qsLogger: QSLogger
     @Mock
-    private lateinit var qsHost: QSTileHost
+    private lateinit var qsHost: QSHost
     @Mock
     private lateinit var metricsLogger: MetricsLogger
     private val falsingManager = FalsingManagerFake()
@@ -135,7 +135,7 @@
     }
 
     private class FakeBluetoothTile(
-        qsTileHost: QSTileHost,
+        qsHost: QSHost,
         backgroundLooper: Looper,
         mainHandler: Handler,
         falsingManager: FalsingManager,
@@ -145,7 +145,7 @@
         qsLogger: QSLogger,
         bluetoothController: BluetoothController
     ) : BluetoothTile(
-        qsTileHost,
+        qsHost,
         backgroundLooper,
         mainHandler,
         falsingManager,
@@ -187,4 +187,4 @@
         `when`(bluetoothController.isBluetoothConnected).thenReturn(false)
         `when`(bluetoothController.isBluetoothConnecting).thenReturn(true)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index b40a20c..18f891c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -42,7 +42,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
@@ -78,7 +78,7 @@
     @Mock
     private NetworkController mNetworkController;
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     SignalCallback mSignalCallback;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
index debe41c..fdb63ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
@@ -37,7 +37,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.FakeSettings;
@@ -56,7 +56,7 @@
 public class ColorCorrectionTileTest extends SysuiTestCase {
 
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
index 3fd2501..60c1a33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
@@ -39,7 +39,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
@@ -61,7 +61,7 @@
     private static final Integer COLOR_INVERSION_ENABLED = 1;
 
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
index 15545a4..71d8aa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
@@ -47,7 +47,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
@@ -69,7 +69,7 @@
     @Mock
     private ActivityStarter mActivityStarter;
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
index d0f851b..c7aba1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
@@ -13,7 +13,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.FlashlightController
@@ -34,7 +34,7 @@
 
     @Mock private lateinit var qsLogger: QSLogger
 
-    @Mock private lateinit var qsHost: QSTileHost
+    @Mock private lateinit var qsHost: QSHost
 
     @Mock private lateinit var metricsLogger: MetricsLogger
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index 257d42a..9d62cfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -15,7 +15,9 @@
  */
 package com.android.systemui.qs.tiles
 
+import android.content.Intent
 import android.os.Handler
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -113,4 +115,11 @@
 
         verify(dialogLaunchAnimator).showFromView(any(), eq(view), nullable(), anyBoolean())
     }
+
+    @Test
+    fun getLongClickIntent_getExpectedIntent() {
+        val intent: Intent? = fontScalingTile.getLongClickIntent()
+
+        assertThat(intent!!.action).isEqualTo(Settings.ACTION_TEXT_READING_SETTINGS)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
index 451e911..4a2ac96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
@@ -37,7 +37,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -60,7 +60,7 @@
     @Rule
     public MockitoRule mRule = MockitoJUnit.rule();
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private HotspotController mHotspotController;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index addca9d..abd9094 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -21,7 +21,6 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-
 import android.os.Handler;
 import android.service.quicksettings.Tile;
 import android.testing.AndroidTestingRunner;
@@ -35,7 +34,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
@@ -56,7 +55,7 @@
 public class InternetTileTest extends SysuiTestCase {
 
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private NetworkController mNetworkController;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
index d2bbc8c..08d10fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -52,7 +52,7 @@
     @Mock
     private lateinit var qsLogger: QSLogger
     @Mock
-    private lateinit var qsHost: QSTileHost
+    private lateinit var qsHost: QSHost
     @Mock
     private lateinit var metricsLogger: MetricsLogger
     private val falsingManager = FalsingManagerFake()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
index cfd3735..9638a45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
@@ -36,7 +36,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 
 import org.junit.Before;
@@ -60,7 +60,7 @@
     @Mock
     private ActivityStarter mActivityStarter;
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
index 8031875..3344a17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
@@ -32,7 +32,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
@@ -53,7 +53,7 @@
     @Mock
     private ActivityStarter mActivityStarter;
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index a1be2f3..24287ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -39,7 +39,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 
@@ -54,7 +54,7 @@
 @SmallTest
 public class QRCodeScannerTileTest extends SysuiTestCase {
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 4f6475f..4722c8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -66,7 +66,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -99,7 +99,7 @@
             .setComponent(new ComponentName(mContext.getPackageName(), "WalletActivity"));
 
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 04b372c..99e5564 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -38,7 +38,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -57,7 +57,7 @@
 @Ignore("b/269171747")
 public class ReduceBrightColorsTileTest extends SysuiTestCase {
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index e9dfd3e..c7eb2f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -38,7 +38,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -71,7 +71,7 @@
     @Mock
     private ActivityStarter mActivityStarter;
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private MetricsLogger mMetricsLogger;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 30debdf..21acc08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -43,7 +43,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.screenrecord.RecordingController;
@@ -65,7 +65,7 @@
     @Mock
     private RecordingController mController;
     @Mock
-    private QSTileHost mHost;
+    private QSHost mHost;
     @Mock
     private KeyguardDismissUtil mKeyguardDismissUtil;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
index 0c070da..3d9f650 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
@@ -32,7 +32,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.BatteryController
@@ -55,7 +55,7 @@
     @Mock private lateinit var uiModeManager: UiModeManager
     @Mock private lateinit var resources: Resources
     @Mock private lateinit var qsLogger: QSLogger
-    @Mock private lateinit var qsHost: QSTileHost
+    @Mock private lateinit var qsHost: QSHost
     @Mock private lateinit var metricsLogger: MetricsLogger
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var activityStarter: ActivityStarter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 452606d..8ee1ea8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -44,6 +44,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -354,7 +355,8 @@
                     mDeviceProvisionedController,
                     mKeyguardStateController,
                     mSettings,
-                    mock(DumpManager.class));
+                    mock(DumpManager.class),
+                    mock(LockPatternUtils.class));
         }
 
         public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
new file mode 100644
index 0000000..cd06465
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+/**
+ * This is a freely configurable implementation of [StatusEvent]. It is intended to be used in
+ * tests.
+ */
+class FakeStatusEvent(
+    override val viewCreator: ViewCreator,
+    override val priority: Int = 50,
+    override var forceVisible: Boolean = false,
+    override val showAnimation: Boolean = true,
+    override var contentDescription: String? = "",
+) : StatusEvent
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
new file mode 100644
index 0000000..08a9f31
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -0,0 +1,470 @@
+/*
+ * 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.statusbar.events
+
+import android.graphics.Rect
+import android.os.Process
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.widget.FrameLayout
+import androidx.core.animation.AnimatorTestRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.statusbar.BatteryStatusChip
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
+
+    @Mock private lateinit var systemEventCoordinator: SystemEventCoordinator
+    @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+    @Mock private lateinit var statusBarContentInsetProvider: StatusBarContentInsetsProvider
+    @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var listener: SystemStatusAnimationCallback
+
+    private lateinit var systemClock: FakeSystemClock
+    private lateinit var chipAnimationController: SystemEventChipAnimationController
+    private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
+    private val fakeFeatureFlags = FakeFeatureFlags()
+
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        fakeFeatureFlags.set(Flags.PLUG_IN_STATUS_BAR_CHIP, true)
+
+        systemClock = FakeSystemClock()
+        chipAnimationController =
+            SystemEventChipAnimationController(
+                mContext,
+                statusBarWindowController,
+                statusBarContentInsetProvider,
+                fakeFeatureFlags
+            )
+
+        // ensure that isTooEarly() check in SystemStatusAnimationScheduler does not return true
+        systemClock.advanceTime(Process.getStartUptimeMillis() + MIN_UPTIME)
+
+        // StatusBarContentInsetProvider is mocked. Ensure that it returns some mocked values.
+        whenever(statusBarContentInsetProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(android.util.Pair(10, 10))
+        whenever(statusBarContentInsetProvider.getStatusBarContentAreaForCurrentRotation())
+            .thenReturn(Rect(10, 0, 990, 100))
+
+        // StatusBarWindowController is mocked. The addViewToWindow function needs to be mocked to
+        // ensure that the chip view is added to a parent view
+        whenever(statusBarWindowController.addViewToWindow(any(), any())).then {
+            val statusbarFake = FrameLayout(mContext)
+            statusbarFake.layout(0, 0, 1000, 100)
+            statusbarFake.addView(
+                it.arguments[0] as View,
+                it.arguments[1] as FrameLayout.LayoutParams
+            )
+        }
+    }
+
+    @Test
+    fun testBatteryStatusEvent_standardAnimationLifecycle() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        val batteryChip = createAndScheduleFakeBatteryEvent()
+
+        // assert that animation is queued
+        assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip debounce delay
+        advanceTimeBy(DEBOUNCE_DELAY + 1)
+        // status chip starts animating in after debounce delay
+        assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(0f, batteryChip.contentView.alpha)
+        assertEquals(0f, batteryChip.view.alpha)
+        verify(listener, times(1)).onSystemEventAnimationBegin()
+
+        // skip appear animation
+        animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+        advanceTimeBy(APPEAR_ANIMATION_DURATION)
+        // assert that status chip is visible
+        assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(1f, batteryChip.contentView.alpha)
+        assertEquals(1f, batteryChip.view.alpha)
+
+        // skip status chip display time
+        advanceTimeBy(DISPLAY_LENGTH + 1)
+        // assert that it is still visible but switched to the ANIMATING_OUT state
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(1f, batteryChip.contentView.alpha)
+        assertEquals(1f, batteryChip.view.alpha)
+        verify(listener, times(1)).onSystemEventAnimationFinish(false)
+
+        // skip disappear animation
+        animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+        // assert that it is not visible anymore
+        assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(0f, batteryChip.contentView.alpha)
+        assertEquals(0f, batteryChip.view.alpha)
+    }
+
+    @Test
+    fun testPrivacyStatusEvent_standardAnimationLifecycle() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        val privacyChip = createAndScheduleFakePrivacyEvent()
+
+        // assert that animation is queued
+        assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip debounce delay
+        advanceTimeBy(DEBOUNCE_DELAY + 1)
+        // status chip starts animating in after debounce delay
+        assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(0f, privacyChip.view.alpha)
+        verify(listener, times(1)).onSystemEventAnimationBegin()
+
+        // skip appear animation
+        animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+        advanceTimeBy(APPEAR_ANIMATION_DURATION + 1)
+        // assert that status chip is visible
+        assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(1f, privacyChip.view.alpha)
+
+        // skip status chip display time
+        advanceTimeBy(DISPLAY_LENGTH + 1)
+        // assert that it is still visible but switched to the ANIMATING_OUT state
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(1f, privacyChip.view.alpha)
+        verify(listener, times(1)).onSystemEventAnimationFinish(true)
+        verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+        // skip transition to persistent dot
+        advanceTimeBy(DISAPPEAR_ANIMATION_DURATION + 1)
+        animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+        // assert that it the dot is now visible
+        assertEquals(SHOWING_PERSISTENT_DOT, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(1f, privacyChip.view.alpha)
+
+        // notify SystemStatusAnimationScheduler to remove persistent dot
+        systemStatusAnimationScheduler.removePersistentDot()
+        // assert that IDLE state is entered
+        assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+        verify(listener, times(1)).onHidePersistentDot()
+    }
+
+    @Test
+    fun testHighPriorityEvent_takesPrecedenceOverScheduledLowPriorityEvent() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        // create and schedule low priority event
+        val batteryChip = createAndScheduleFakeBatteryEvent()
+        batteryChip.view.alpha = 0f
+
+        // assert that animation is queued
+        assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+        // create and schedule high priority event
+        val privacyChip = createAndScheduleFakePrivacyEvent()
+
+        // assert that animation is queued
+        assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip debounce delay and appear animation duration
+        fastForwardAnimationToState(RUNNING_CHIP_ANIM)
+
+        // high priority status chip is visible while low priority status chip is not visible
+        assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(1f, privacyChip.view.alpha)
+        assertEquals(0f, batteryChip.view.alpha)
+    }
+
+    @Test
+    fun testHighPriorityEvent_cancelsCurrentlyDisplayedLowPriorityEvent() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        // create and schedule low priority event
+        val batteryChip = createAndScheduleFakeBatteryEvent()
+
+        // fast forward to RUNNING_CHIP_ANIM state
+        fastForwardAnimationToState(RUNNING_CHIP_ANIM)
+
+        // assert that chip is displayed
+        assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(1f, batteryChip.view.alpha)
+
+        // create and schedule high priority event
+        val privacyChip = createAndScheduleFakePrivacyEvent()
+
+        // ensure that the event cancellation coroutine is started by the test scope
+        testScheduler.runCurrent()
+
+        // assert that currently displayed chip is immediately animated out
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip disappear animation
+        animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+
+        // assert that high priority privacy chip animation is queued
+        assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip debounce delay and appear animation
+        advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1)
+        animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+
+        // high priority status chip is visible while low priority status chip is not visible
+        assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(1f, privacyChip.view.alpha)
+        assertEquals(0f, batteryChip.view.alpha)
+    }
+
+    @Test
+    fun testHighPriorityEvent_cancelsCurrentlyAnimatedLowPriorityEvent() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        // create and schedule low priority event
+        val batteryChip = createAndScheduleFakeBatteryEvent()
+
+        // skip debounce delay
+        advanceTimeBy(DEBOUNCE_DELAY + 1)
+
+        // assert that chip is animated in
+        assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState())
+
+        // create and schedule high priority event
+        val privacyChip = createAndScheduleFakePrivacyEvent()
+
+        // ensure that the event cancellation coroutine is started by the test scope
+        testScheduler.runCurrent()
+
+        // assert that currently animated chip keeps animating
+        assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip appear animation
+        animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+        advanceTimeBy(APPEAR_ANIMATION_DURATION + 1)
+
+        // assert that low priority chip is animated out immediately after finishing the appear
+        // animation
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip disappear animation
+        animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+
+        // assert that high priority privacy chip animation is queued
+        assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip debounce delay and appear animation
+        advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1)
+        animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+
+        // high priority status chip is visible while low priority status chip is not visible
+        assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(1f, privacyChip.view.alpha)
+        assertEquals(0f, batteryChip.view.alpha)
+    }
+
+    @Test
+    fun testHighPriorityEvent_isNotReplacedByLowPriorityEvent() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        // create and schedule high priority event
+        val privacyChip = createAndScheduleFakePrivacyEvent()
+
+        // create and schedule low priority event
+        val batteryChip = createAndScheduleFakeBatteryEvent()
+        batteryChip.view.alpha = 0f
+
+        // skip debounce delay and appear animation
+        advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1)
+        animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+
+        // high priority status chip is visible while low priority status chip is not visible
+        assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+        assertEquals(1f, privacyChip.view.alpha)
+        assertEquals(0f, batteryChip.view.alpha)
+    }
+
+    @Test
+    fun testPrivacyDot_isRemoved() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        // create and schedule high priority event
+        createAndScheduleFakePrivacyEvent()
+
+        // skip chip animation lifecycle and fast forward to SHOWING_PERSISTENT_DOT state
+        fastForwardAnimationToState(SHOWING_PERSISTENT_DOT)
+        assertEquals(SHOWING_PERSISTENT_DOT, systemStatusAnimationScheduler.getAnimationState())
+        verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+        // remove persistent dot and verify that animationState changes to IDLE
+        systemStatusAnimationScheduler.removePersistentDot()
+        assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+        verify(listener, times(1)).onHidePersistentDot()
+    }
+
+    @Test
+    fun testPrivacyDot_isRemovedDuringChipAnimation() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        // create and schedule high priority event
+        createAndScheduleFakePrivacyEvent()
+
+        // skip chip animation lifecycle and fast forward to RUNNING_CHIP_ANIM state
+        fastForwardAnimationToState(RUNNING_CHIP_ANIM)
+        assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+
+        // request removal of persistent dot
+        systemStatusAnimationScheduler.removePersistentDot()
+
+        // skip display time and verify that disappear animation is run
+        advanceTimeBy(DISPLAY_LENGTH + 1)
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip disappear animation and verify that animationState changes to IDLE instead of
+        // SHOWING_PERSISTENT_DOT
+        animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+        assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+        // verify that the persistent dot callbacks are not invoked
+        verify(listener, never()).onSystemStatusAnimationTransitionToPersistentDot(any())
+        verify(listener, never()).onHidePersistentDot()
+    }
+
+    @Test
+    fun testNewEvent_isScheduled_whenPostedDuringRemovalAnimation() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        // create and schedule high priority event
+        createAndScheduleFakePrivacyEvent()
+
+        // skip chip animation lifecycle and fast forward to ANIMATING_OUT state
+        fastForwardAnimationToState(ANIMATING_OUT)
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+        verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+        // request removal of persistent dot
+        systemStatusAnimationScheduler.removePersistentDot()
+        testScheduler.runCurrent()
+
+        // schedule another high priority event while the event is animating out
+        createAndScheduleFakePrivacyEvent()
+
+        // verify that the state is still ANIMATING_OUT
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip disappear animation duration and verify that new state is ANIMATION_QUEUED
+        animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+        testScheduler.runCurrent()
+        assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+        // also verify that onHidePersistentDot callback is called
+        verify(listener, times(1)).onHidePersistentDot()
+    }
+
+    private fun TestScope.fastForwardAnimationToState(@SystemAnimationState animationState: Int) {
+        // this function should only be called directly after posting a status event
+        assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+        if (animationState == IDLE || animationState == ANIMATION_QUEUED) return
+        // skip debounce delay
+        advanceTimeBy(DEBOUNCE_DELAY + 1)
+
+        // status chip starts animating in after debounce delay
+        assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState())
+        verify(listener, times(1)).onSystemEventAnimationBegin()
+        if (animationState == ANIMATING_IN) return
+
+        // skip appear animation
+        animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+        advanceTimeBy(APPEAR_ANIMATION_DURATION)
+        assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+        if (animationState == RUNNING_CHIP_ANIM) return
+
+        // skip status chip display time
+        advanceTimeBy(DISPLAY_LENGTH + 1)
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+        verify(listener, times(1)).onSystemEventAnimationFinish(anyBoolean())
+        if (animationState == ANIMATING_OUT) return
+
+        // skip disappear animation
+        animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+    }
+
+    private fun createAndScheduleFakePrivacyEvent(): OngoingPrivacyChip {
+        val privacyChip = OngoingPrivacyChip(mContext)
+        val fakePrivacyStatusEvent =
+            FakeStatusEvent(viewCreator = { privacyChip }, priority = 100, forceVisible = true)
+        systemStatusAnimationScheduler.onStatusEvent(fakePrivacyStatusEvent)
+        return privacyChip
+    }
+
+    private fun createAndScheduleFakeBatteryEvent(): BatteryStatusChip {
+        val batteryChip = BatteryStatusChip(mContext)
+        val fakeBatteryEvent =
+            FakeStatusEvent(viewCreator = { batteryChip }, priority = 50, forceVisible = false)
+        systemStatusAnimationScheduler.onStatusEvent(fakeBatteryEvent)
+        return batteryChip
+    }
+
+    private fun initializeSystemStatusAnimationScheduler(testScope: TestScope) {
+        systemStatusAnimationScheduler =
+            SystemStatusAnimationSchedulerImpl(
+                systemEventCoordinator,
+                chipAnimationController,
+                statusBarWindowController,
+                dumpManager,
+                systemClock,
+                CoroutineScope(StandardTestDispatcher(testScope.testScheduler))
+            )
+        // add a mock listener
+        systemStatusAnimationScheduler.addCallback(listener)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt
new file mode 100644
index 0000000..2de21ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import android.view.View
+
+class FakeNodeController(
+    override val view: View,
+    override val nodeLabel: String = "fakeNodeController"
+) : NodeController {
+    override fun offerToKeepInParentForAnimation(): Boolean = false
+    override fun removeFromParentIfKeptForAnimation(): Boolean = false
+    override fun resetKeepInParentForAnimation() = Unit
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 5b6c8c6..fb3aba1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -31,15 +31,19 @@
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.SmartReplyController
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider
+import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.SmartReplyConstants
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.SystemClock
 import com.android.systemui.wmshell.BubblesManager
@@ -68,6 +72,7 @@
     private val metricsLogger: MetricsLogger = mock()
     private val logBufferLogger: NotificationRowLogger = mock()
     private val listContainer: NotificationListContainer = mock()
+    private val childrenContainer: NotificationChildrenContainer = mock()
     private val mediaManager: NotificationMediaManager = mock()
     private val smartReplyConstants: SmartReplyConstants = mock()
     private val smartReplyController: SmartReplyController = mock()
@@ -129,6 +134,7 @@
                 dragController,
                 dismissibilityProvider
             )
+        whenever(view.childrenContainer).thenReturn(childrenContainer)
     }
 
     @After
@@ -173,4 +179,32 @@
         Assert.assertFalse(controller.removeFromParentIfKeptForAnimation())
         Mockito.verifyNoMoreInteractions(parentView)
     }
+
+    @Test
+    fun removeChild_whenTransfer() {
+        val childView: ExpandableNotificationRow = mock()
+        val childNodeController = FakeNodeController(childView)
+
+        // GIVEN a child is removed for transfer
+        controller.removeChild(childNodeController, /* isTransfer= */ true)
+
+        // VERIFY the listContainer is not notified
+        Mockito.verify(childView).isChangingPosition = eq(true)
+        Mockito.verify(view).removeChildNotification(eq(childView))
+        Mockito.verify(listContainer, never()).notifyGroupChildRemoved(any(), any())
+    }
+
+    @Test
+    fun removeChild_whenNotTransfer() {
+        val childView: ExpandableNotificationRow = mock()
+        val childNodeController = FakeNodeController(childView)
+
+        // GIVEN a child is removed for real
+        controller.removeChild(childNodeController, /* isTransfer= */ false)
+
+        // VERIFY the listContainer is passed the childrenContainer for transient animations
+        Mockito.verify(childView, never()).isChangingPosition = any()
+        Mockito.verify(view).removeChildNotification(eq(childView))
+        Mockito.verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer))
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index f568547..e680a4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -53,7 +53,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.external.CustomTile;
@@ -104,7 +104,7 @@
 
     private static final int USER = 0;
 
-    @Mock private QSTileHost mQsTileHost;
+    @Mock private QSHost mQsHost;
     @Mock private AutoAddTracker mAutoAddTracker;
     @Mock private CastController mCastController;
     @Mock private HotspotController mHotspotController;
@@ -144,7 +144,7 @@
                 R.string.safety_quick_settings_tile_class, TEST_CUSTOM_SAFETY_CLASS);
 
         when(mAutoAddTrackerBuilder.build()).thenReturn(mAutoAddTracker);
-        when(mQsTileHost.getUserContext()).thenReturn(mUserContext);
+        when(mQsHost.getUserContext()).thenReturn(mUserContext);
         when(mUserContext.getUser()).thenReturn(UserHandle.of(USER));
         mPackageManager = Mockito.spy(mContext.getPackageManager());
         when(mPackageManager.getPermissionControllerPackageName())
@@ -174,7 +174,7 @@
             WalletController walletController,
             SafetyController safetyController,
             @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
-        return new AutoTileManager(context, autoAddTrackerBuilder, mQsTileHost,
+        return new AutoTileManager(context, autoAddTrackerBuilder, mQsHost,
                 Handler.createAsync(TestableLooper.get(this).getLooper()),
                 mSecureSettings,
                 hotspotController,
@@ -359,7 +359,7 @@
             return;
         }
         mAutoTileManager.mNightDisplayCallback.onActivated(true);
-        verify(mQsTileHost).addTile("night");
+        verify(mQsHost).addTile("night");
     }
 
     @Test
@@ -368,7 +368,7 @@
             return;
         }
         mAutoTileManager.mNightDisplayCallback.onActivated(false);
-        verify(mQsTileHost, never()).addTile("night");
+        verify(mQsHost, never()).addTile("night");
     }
 
     @Test
@@ -378,7 +378,7 @@
         }
         mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
                 ColorDisplayManager.AUTO_MODE_TWILIGHT);
-        verify(mQsTileHost).addTile("night");
+        verify(mQsHost).addTile("night");
     }
 
     @Test
@@ -388,7 +388,7 @@
         }
         mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
                 ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
-        verify(mQsTileHost).addTile("night");
+        verify(mQsHost).addTile("night");
     }
 
     @Test
@@ -398,19 +398,19 @@
         }
         mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
                 ColorDisplayManager.AUTO_MODE_DISABLED);
-        verify(mQsTileHost, never()).addTile("night");
+        verify(mQsHost, never()).addTile("night");
     }
 
     @Test
     public void reduceBrightColorsTileAdded_whenActivated() {
         mAutoTileManager.mReduceBrightColorsCallback.onActivated(true);
-        verify(mQsTileHost).addTile("reduce_brightness");
+        verify(mQsHost).addTile("reduce_brightness");
     }
 
     @Test
     public void reduceBrightColorsTileNotAdded_whenDeactivated() {
         mAutoTileManager.mReduceBrightColorsCallback.onActivated(false);
-        verify(mQsTileHost, never()).addTile("reduce_brightness");
+        verify(mQsHost, never()).addTile("reduce_brightness");
     }
 
     private static List<CastDevice> buildFakeCastDevice(boolean isCasting) {
@@ -423,28 +423,28 @@
     public void castTileAdded_whenDeviceIsCasting() {
         doReturn(buildFakeCastDevice(true)).when(mCastController).getCastDevices();
         mAutoTileManager.mCastCallback.onCastDevicesChanged();
-        verify(mQsTileHost).addTile("cast");
+        verify(mQsHost).addTile("cast");
     }
 
     @Test
     public void castTileNotAdded_whenDeviceIsNotCasting() {
         doReturn(buildFakeCastDevice(false)).when(mCastController).getCastDevices();
         mAutoTileManager.mCastCallback.onCastDevicesChanged();
-        verify(mQsTileHost, never()).addTile("cast");
+        verify(mQsHost, never()).addTile("cast");
     }
 
     @Test
     public void testSettingTileAdded_onChanged() {
         changeValue(TEST_SETTING, 1);
         verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
-        verify(mQsTileHost).addTile(TEST_SPEC);
+        verify(mQsHost).addTile(TEST_SPEC);
     }
 
     @Test
     public void testSettingTileAddedComponentAtEnd_onChanged() {
         changeValue(TEST_SETTING_COMPONENT, 1);
         verify(mAutoAddTracker).setTileAdded(TEST_CUSTOM_SPEC);
-        verify(mQsTileHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)
+        verify(mQsHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)
             , /* end */ true);
     }
 
@@ -453,14 +453,14 @@
         changeValue(TEST_SETTING, 1);
         changeValue(TEST_SETTING, 2);
         verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
-        verify(mQsTileHost).addTile(TEST_SPEC);
+        verify(mQsHost).addTile(TEST_SPEC);
     }
 
     @Test
     public void testSettingTileNotAdded_onChangedTo0() {
         changeValue(TEST_SETTING, 0);
         verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
-        verify(mQsTileHost, never()).addTile(TEST_SPEC);
+        verify(mQsHost, never()).addTile(TEST_SPEC);
     }
 
     @Test
@@ -469,27 +469,27 @@
 
         changeValue(TEST_SETTING, 1);
         verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
-        verify(mQsTileHost, never()).addTile(TEST_SPEC);
+        verify(mQsHost, never()).addTile(TEST_SPEC);
     }
 
     @Test
     public void testSafetyTileNotAdded_ifPreviouslyAdded() {
         ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
         mAutoTileManager.init();
-        verify(mQsTileHost, times(1)).addTile(safetyComponent, true);
+        verify(mQsHost, times(1)).addTile(safetyComponent, true);
         when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
         mAutoTileManager.init();
-        verify(mQsTileHost, times(1)).addTile(safetyComponent, true);
+        verify(mQsHost, times(1)).addTile(safetyComponent, true);
     }
 
     @Test
     public void testSafetyTileAdded_onUserChange() {
         ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
         mAutoTileManager.init();
-        verify(mQsTileHost, times(1)).addTile(safetyComponent, true);
+        verify(mQsHost, times(1)).addTile(safetyComponent, true);
         when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(false);
         mAutoTileManager.changeUser(UserHandle.of(USER + 1));
-        verify(mQsTileHost, times(2)).addTile(safetyComponent, true);
+        verify(mQsHost, times(2)).addTile(safetyComponent, true);
     }
 
     @Test
@@ -498,17 +498,17 @@
         mAutoTileManager.init();
         when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
         mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
-        verify(mQsTileHost, times(1)).removeTile(TEST_CUSTOM_SAFETY_SPEC);
+        verify(mQsHost, times(1)).removeTile(TEST_CUSTOM_SAFETY_SPEC);
     }
 
     @Test
     public void testSafetyTileAdded_onSafetyCenterEnable() {
         ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
         mAutoTileManager.init();
-        verify(mQsTileHost, times(1)).addTile(safetyComponent, true);
+        verify(mQsHost, times(1)).addTile(safetyComponent, true);
         mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
         mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(true);
-        verify(mQsTileHost, times(2)).addTile(safetyComponent, true);
+        verify(mQsHost, times(2)).addTile(safetyComponent, true);
     }
 
     @Test
@@ -525,7 +525,7 @@
 
         mManagedProfileCallback.onManagedProfileChanged();
 
-        verify(mQsTileHost, times(1)).addTile(eq("work"), eq(2));
+        verify(mQsHost, times(1)).addTile(eq("work"), eq(2));
         verify(mAutoAddTracker, times(1)).setTileAdded(eq("work"));
     }
 
@@ -542,7 +542,7 @@
 
         mManagedProfileCallback.onManagedProfileChanged();
 
-        verify(mQsTileHost, times(1)).removeTile(eq("work"));
+        verify(mQsHost, times(1)).removeTile(eq("work"));
         verify(mAutoAddTracker, times(1)).setTileRemoved(eq("work"));
     }
 
@@ -550,7 +550,7 @@
     public void testAddControlsTileIfNotPresent() {
         String spec = DEVICE_CONTROLS;
         when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
-        when(mQsTileHost.getTiles()).thenReturn(new ArrayList<>());
+        when(mQsHost.getTiles()).thenReturn(new ArrayList<>());
 
         mAutoTileManager.init();
         ArgumentCaptor<DeviceControlsController.Callback> captor =
@@ -559,7 +559,7 @@
         verify(mDeviceControlsController).setCallback(captor.capture());
 
         captor.getValue().onControlsUpdate(3);
-        verify(mQsTileHost).addTile(spec, 3);
+        verify(mQsHost).addTile(spec, 3);
         verify(mAutoAddTracker).setTileAdded(spec);
     }
 
@@ -567,7 +567,7 @@
     public void testDontAddControlsTileIfPresent() {
         String spec = DEVICE_CONTROLS;
         when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
-        when(mQsTileHost.getTiles()).thenReturn(new ArrayList<>());
+        when(mQsHost.getTiles()).thenReturn(new ArrayList<>());
 
         mAutoTileManager.init();
         ArgumentCaptor<DeviceControlsController.Callback> captor =
@@ -576,7 +576,7 @@
         verify(mDeviceControlsController).setCallback(captor.capture());
 
         captor.getValue().removeControlsAutoTracker();
-        verify(mQsTileHost, never()).addTile(spec, 3);
+        verify(mQsHost, never()).addTile(spec, 3);
         verify(mAutoAddTracker, never()).setTileAdded(spec);
         verify(mAutoAddTracker).setTileRemoved(spec);
     }
@@ -587,7 +587,7 @@
         when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(true);
         QSTile mockTile = mock(QSTile.class);
         when(mockTile.getTileSpec()).thenReturn(spec);
-        when(mQsTileHost.getTiles()).thenReturn(List.of(mockTile));
+        when(mQsHost.getTiles()).thenReturn(List.of(mockTile));
 
         mAutoTileManager.init();
         ArgumentCaptor<DeviceControlsController.Callback> captor =
@@ -596,7 +596,7 @@
         verify(mDeviceControlsController).setCallback(captor.capture());
 
         captor.getValue().onControlsUpdate(3);
-        verify(mQsTileHost, never()).addTile(spec, 3);
+        verify(mQsHost, never()).addTile(spec, 3);
         verify(mAutoAddTracker, never()).setTileAdded(spec);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 1e2fe35..31a1e4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -94,6 +95,7 @@
     @Mock private SystemBarAttributesListener mSystemBarAttributesListener;
     @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
     @Mock private UserTracker mUserTracker;
+    @Mock private QSHost mQSHost;
 
     CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
 
@@ -128,7 +130,8 @@
                 DEFAULT_DISPLAY,
                 mSystemBarAttributesListener,
                 mCameraLauncherLazy,
-                mUserTracker);
+                mUserTracker,
+                mQSHost);
 
         when(mUserTracker.getUserHandle()).thenReturn(
                 UserHandle.of(ActivityManager.getCurrentUser()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
index 1582cee..7d9c091 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
@@ -32,7 +32,7 @@
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.wm.shell.bubbles.Bubbles;
 
@@ -62,7 +62,7 @@
     @Mock
     DozeParameters mDozeParameters;
     @Mock
-    CommonNotifCollection mNotifCollection;
+    SectionStyleProvider mSectionStyleProvider;
     @Mock
     DarkIconDispatcher mDarkIconDispatcher;
     @Mock
@@ -87,6 +87,7 @@
                 mNotificationMediaManager,
                 mListener,
                 mDozeParameters,
+                mSectionStyleProvider,
                 Optional.of(mBubbles),
                 mDemoModeController,
                 mDarkIconDispatcher,
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 85e8c34..64545b1 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
@@ -32,7 +32,6 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import android.animation.Animator;
 import android.app.Fragment;
 import android.app.StatusBarManager;
 import android.content.Context;
@@ -45,6 +44,7 @@
 import android.view.ViewPropertyAnimator;
 import android.widget.FrameLayout;
 
+import androidx.core.animation.Animator;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 0926f8a..a359216 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -142,7 +142,7 @@
     protected final SystemSupport mSystemSupport;
     protected final WindowManagerInternal mWindowManagerService;
     private final SystemActionPerformer mSystemActionPerformer;
-    private final AccessibilityWindowManager mA11yWindowManager;
+    final AccessibilityWindowManager mA11yWindowManager;
     private final DisplayManager mDisplayManager;
     private final PowerManager mPowerManager;
     private final IPlatformCompat mIPlatformCompat;
@@ -2006,15 +2006,14 @@
         return accessibilityWindowId;
     }
 
-    private int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
+    int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
         if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
             final int focusedWindowId = mA11yWindowManager.getFocusedWindowId(focusType);
             // If the caller is a proxy and the found window doesn't belong to a proxy display
             // (or vice versa), then return null. This doesn't work if there are multiple active
-            // proxys, but in the future this code shouldn't be needed if input and a11y focus are
+            // proxys, but in the future this code shouldn't be needed if input focus are
             // properly split. (so we will deal with the issues if we see them).
-            //TODO(254545943): Remove this when there is user and proxy separation of input and a11y
-            // focus
+            //TODO(254545943): Remove this when there is user and proxy separation of input focus
             if (!mA11yWindowManager.windowIdBelongsToDisplayType(focusedWindowId, mDisplayTypes)) {
                 return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index cde820a..1f8a779 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3823,7 +3823,7 @@
         try {
             mProxyManager.registerProxy(client, displayId, mContext,
                     sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(),
-                    mWindowManagerService, mA11yWindowManager);
+                    mWindowManagerService);
 
             synchronized (mLock) {
                 notifyClearAccessibilityCacheLocked();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index f0c6c4f..094053e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -19,6 +19,7 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
@@ -104,6 +105,8 @@
 
     private boolean mTouchInteractionInProgress;
 
+    private boolean mHasProxy;
+
     /** List of Display Windows Observer, mapping from displayId -> DisplayWindowsObserver. */
     private final SparseArray<DisplayWindowsObserver> mDisplayWindowsObservers =
             new SparseArray<>();
@@ -159,6 +162,9 @@
      * Returns {@code true} if the window belongs to a display of {@code displayTypes}.
      */
     public boolean windowIdBelongsToDisplayType(int focusedWindowId, int displayTypes) {
+        if (!mHasProxy) {
+            return true;
+        }
         // UIAutomation wants focus from any display type.
         final int displayTypeMask = DISPLAY_TYPE_PROXY | DISPLAY_TYPE_DEFAULT;
         if ((displayTypes & displayTypeMask) == displayTypeMask) {
@@ -195,6 +201,7 @@
         private List<AccessibilityWindowInfo> mWindows;
         private boolean mTrackingWindows = false;
         private boolean mHasWatchOutsideTouchWindow;
+        private int mProxyDisplayAccessibilityFocusedWindow;
         private boolean mIsProxy;
 
         /**
@@ -608,8 +615,11 @@
 
             final int windowCount = windows.size();
             final boolean isTopFocusedDisplay = mDisplayId == mTopFocusedDisplayId;
+            // A proxy with an a11y-focused window is a11y-focused should use the proxy focus id.
             final boolean isAccessibilityFocusedDisplay =
-                    mDisplayId == mAccessibilityFocusedDisplayId;
+                    mDisplayId == mAccessibilityFocusedDisplayId
+                            || (mIsProxy && mProxyDisplayAccessibilityFocusedWindow
+                                    != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
             // Modifies the value of top focused window, active window and a11y focused window
             // only if this display is top focused display which has the top focused window.
             if (isTopFocusedDisplay) {
@@ -635,9 +645,12 @@
 
             // We'll clear accessibility focus if the window with focus is no longer visible to
             // accessibility services.
+            int a11yFocusedWindowId = mIsProxy
+                    ? mProxyDisplayAccessibilityFocusedWindow
+                    : mAccessibilityFocusedWindowId;
             if (isAccessibilityFocusedDisplay) {
-                shouldClearAccessibilityFocus = mAccessibilityFocusedWindowId
-                    != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+                shouldClearAccessibilityFocus = a11yFocusedWindowId
+                        != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
             }
 
             boolean hasWindowIgnore = false;
@@ -701,7 +714,7 @@
                 if (isAccessibilityFocusedDisplay) {
                     for (int i = 0; i < accessibilityWindowCount; i++) {
                         final AccessibilityWindowInfo window = mWindows.get(i);
-                        if (window.getId() == mAccessibilityFocusedWindowId) {
+                        if (window.getId() == a11yFocusedWindowId) {
                             window.setAccessibilityFocused(true);
                             shouldClearAccessibilityFocus = false;
                             break;
@@ -718,7 +731,7 @@
             }
 
             if (shouldClearAccessibilityFocus) {
-                clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId);
+                clearAccessibilityFocusLocked(a11yFocusedWindowId);
             }
         }
 
@@ -1022,6 +1035,7 @@
             }
             if (proxyed && !observer.mIsProxy) {
                 observer.mIsProxy = true;
+                mHasProxy = true;
             }
             if (observer.isTrackingWindowsLocked()) {
                 return;
@@ -1044,6 +1058,7 @@
                 observer.stopTrackingWindowsLocked();
                 mDisplayWindowsObservers.remove(displayId);
             }
+            resetHasProxyIfNeededLocked();
         }
     }
 
@@ -1053,11 +1068,26 @@
      */
     public void stopTrackingDisplayProxy(int displayId) {
         synchronized (mLock) {
-            DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
+            final DisplayWindowsObserver proxyObserver = mDisplayWindowsObservers.get(displayId);
+            if (proxyObserver != null) {
+                proxyObserver.mIsProxy = false;
+            }
+            resetHasProxyIfNeededLocked();
+        }
+    }
+
+    private void resetHasProxyIfNeededLocked() {
+        boolean hasProxy = false;
+        final int count = mDisplayWindowsObservers.size();
+        for (int i = 0; i < count; i++) {
+            final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
             if (observer != null) {
-                observer.mIsProxy = false;
+                if (observer.mIsProxy) {
+                    hasProxy = true;
+                }
             }
         }
+        mHasProxy = hasProxy;
     }
 
     /**
@@ -1490,6 +1520,11 @@
 
             case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
                 synchronized (mLock) {
+                    // If window id belongs to a proxy display, then find the display, update the
+                    // observer focus and send WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED events.
+                    if (mHasProxy && setProxyFocusLocked(windowId)) {
+                        return;
+                    }
                     if (mAccessibilityFocusedWindowId != windowId) {
                         clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId);
                         setAccessibilityFocusedWindowLocked(windowId);
@@ -1500,6 +1535,10 @@
 
             case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
                 synchronized (mLock) {
+                    // If cleared happened on the proxy display, then clear the tracked focus.
+                    if (mHasProxy && clearProxyFocusLocked(windowId, eventAction)) {
+                        return;
+                    }
                     if (mAccessibilityFocusNodeId == nodeId) {
                         mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
                     }
@@ -1599,9 +1638,10 @@
             if (mAccessibilityFocusedDisplayId != Display.INVALID_DISPLAY
                     && mAccessibilityFocusedWindowId
                     != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+                // Previously focused window -> send a focused event for losing focus
                 events.add(AccessibilityEvent.obtainWindowsChangedEvent(
                         mAccessibilityFocusedDisplayId, mAccessibilityFocusedWindowId,
-                        AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+                        WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
             }
 
             mAccessibilityFocusedWindowId = windowId;
@@ -1611,8 +1651,9 @@
                 final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
                 if (observer != null && observer.setAccessibilityFocusedWindowLocked(windowId)) {
                     mAccessibilityFocusedDisplayId = observer.mDisplayId;
+                    // Newly focused window -> send a focused event for gaining focus
                     events.add(AccessibilityEvent.obtainWindowsChangedEvent(observer.mDisplayId,
-                            windowId, AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+                            windowId, WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
                 }
             }
 
@@ -1662,6 +1703,33 @@
      * @return The focused windowId
      */
     public int getFocusedWindowId(int focusType) {
+        return getFocusedWindowId(focusType, Display.INVALID_DISPLAY);
+    }
+
+    /**
+     * Returns focused windowId or accessibility focused windowId according to given focusType and
+     * display id.
+     * @param focusType {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+     * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}
+     * @param displayId the display id to check. If this display is proxy-ed, the proxy's a11y focus
+     *                  will be returned.
+     * @return The focused windowId
+     */
+    public int getFocusedWindowId(int focusType, int displayId) {
+        if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY
+                || !mHasProxy) {
+            return getDefaultFocus(focusType);
+        }
+
+        final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
+        if (observer != null && observer.mIsProxy) {
+            return getProxyFocus(focusType, observer);
+        } else {
+            return getDefaultFocus(focusType);
+        }
+    }
+
+    private int getDefaultFocus(int focusType) {
         if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
             return mTopFocusedWindowId;
         } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
@@ -1670,6 +1738,16 @@
         return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
     }
 
+    private int getProxyFocus(int focusType, DisplayWindowsObserver observer) {
+        if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
+            return mTopFocusedWindowId;
+        } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
+            return observer.mProxyDisplayAccessibilityFocusedWindow;
+        } else {
+            return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+        }
+    }
+
     /**
      * Returns {@link AccessibilityWindowInfo} of PIP window.
      *
@@ -1988,6 +2066,78 @@
     }
 
     /**
+     * Checks if the window belongs to a proxy display and if so clears the focused window id.
+     * @param focusClearedWindowId the cleared window id.
+     * @return true if an observer is proxy-ed and has cleared its focused window id.
+     */
+    private boolean clearProxyFocusLocked(int focusClearedWindowId, int eventAction) {
+        // If we are just moving focus from one view to the other in the same window, do nothing.
+        if (eventAction == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
+            return false;
+        }
+        for (int i = 0; i < mDisplayWindowsObservers.size(); i++) {
+            final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(i);
+            if (observer != null && observer.mWindows != null && observer.mIsProxy) {
+                final int windowCount = observer.mWindows.size();
+                for (int j = 0; j < windowCount; j++) {
+                    AccessibilityWindowInfo window = observer.mWindows.get(j);
+                    if (window.getId() == focusClearedWindowId) {
+                        observer.mProxyDisplayAccessibilityFocusedWindow =
+                                AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+                        // TODO(268754409): Look into sending a WINDOW_FOCUS_CHANGED event since
+                        //  window no longer has focus (default window logic doesn't), and
+                        //  whether the node id needs to be cached (default window logic does).
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checks if the window belongs to a proxy display and if so sends
+     * WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED for that window and the previously focused window.
+     * @param focusedWindowId the focused window id.
+     * @return true if an observer is proxy-ed and contains the focused window.
+     */
+    private boolean setProxyFocusLocked(int focusedWindowId) {
+        for (int i = 0; i < mDisplayWindowsObservers.size(); i++) {
+            final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
+            if (observer != null && observer.mIsProxy
+                    && observer.setAccessibilityFocusedWindowLocked(focusedWindowId)) {
+                final int previouslyFocusedWindowId =
+                        observer.mProxyDisplayAccessibilityFocusedWindow;
+
+                if (previouslyFocusedWindowId == focusedWindowId) {
+                    // Don't send a focus event if the window is already focused.
+                    return true;
+                }
+
+                // Previously focused window -> Clear focus on UI thread and send a focused event
+                // for losing focus
+                if (previouslyFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+                    clearAccessibilityFocusLocked(previouslyFocusedWindowId);
+                    mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
+                            AccessibilityEvent.obtainWindowsChangedEvent(
+                                    observer.mDisplayId, previouslyFocusedWindowId,
+                                    WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+                }
+                observer.mProxyDisplayAccessibilityFocusedWindow = focusedWindowId;
+                // Newly focused window -> send a focused event for it gaining focus
+                mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
+                        AccessibilityEvent.obtainWindowsChangedEvent(
+                                observer.mDisplayId,
+                                observer.mProxyDisplayAccessibilityFocusedWindow,
+                                WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Dumps all {@link AccessibilityWindowInfo}s here.
      */
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index df913aa..945d43b 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -255,6 +255,24 @@
     }
 
     @Override
+    int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
+        if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
+            final int focusedWindowId = mA11yWindowManager.getFocusedWindowId(focusType,
+                    mDisplayId);
+            // If the caller is a proxy and the found window doesn't belong to a proxy display
+            // (or vice versa), then return null. This doesn't work if there are multiple active
+            // proxys, but in the future this code shouldn't be needed if input focus
+            // properly split. (so we will deal with the issues if we see them).
+            //TODO(254545943): Remove this when there is user and proxy separation of input
+            if (!mA11yWindowManager.windowIdBelongsToDisplayType(focusedWindowId, mDisplayTypes)) {
+                return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+            }
+            return focusedWindowId;
+        }
+        return windowId;
+    }
+
+    @Override
     public void binderDied() {
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 2530338..9d91d10 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -78,8 +78,7 @@
             AccessibilitySecurityPolicy securityPolicy,
             AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
             AccessibilityTrace trace,
-            WindowManagerInternal windowManagerInternal,
-            AccessibilityWindowManager awm) throws RemoteException {
+            WindowManagerInternal windowManagerInternal) throws RemoteException {
 
         // Set a default AccessibilityServiceInfo that is used before the proxy's info is
         // populated. A proxy has the touch exploration and window capabilities.
@@ -93,7 +92,7 @@
                 new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
                         id, mainHandler, mLock, securityPolicy, systemSupport, trace,
                         windowManagerInternal,
-                        awm, displayId);
+                        mA11yWindowManager, displayId);
 
         synchronized (mLock) {
             mProxyA11yServiceConnections.put(displayId, connection);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 9c84c04..f85ef43f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -44,6 +44,8 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.MathUtils;
@@ -279,7 +281,8 @@
         return mTempPointerProperties;
     }
 
-    private void transitionTo(State state) {
+    @VisibleForTesting
+    void transitionTo(State state) {
         if (DEBUG_STATE_TRANSITIONS) {
             Slog.i(mLogTag,
                     (State.nameOf(mCurrentState) + " -> " + State.nameOf(state)
@@ -287,6 +290,9 @@
                     .replace(getClass().getName(), ""));
         }
         mPreviousState = mCurrentState;
+        if (state == mPanningScalingState) {
+            mPanningScalingState.prepareForState();
+        }
         mCurrentState = state;
     }
 
@@ -317,18 +323,34 @@
     final class PanningScalingState extends SimpleOnGestureListener
             implements OnScaleGestureListener, State {
 
+        private final Context mContext;
         private final ScaleGestureDetector mScaleGestureDetector;
         private final GestureDetector mScrollGestureDetector;
         final float mScalingThreshold;
 
         float mInitialScaleFactor = -1;
-        boolean mScaling;
+        @VisibleForTesting boolean mScaling;
+
+        /**
+         * Whether it needs to detect the target scale passes
+         * {@link FullScreenMagnificationController#getPersistedScale} during panning scale.
+         */
+        @VisibleForTesting boolean mDetectingPassPersistedScale;
+
+        // The threshold for relative difference from given scale to persisted scale. If the
+        // difference >= threshold, we can start detecting if the scale passes the persisted
+        // scale during panning.
+        @VisibleForTesting static final float CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD = 0.2f;
+        // The threshold for relative difference from given scale to persisted scale. If the
+        // difference < threshold, we can decide that the scale passes the persisted scale.
+        @VisibleForTesting static final float PASSING_PERSISTED_SCALE_THRESHOLD = 0.01f;
 
         PanningScalingState(Context context) {
             final TypedValue scaleValue = new TypedValue();
             context.getResources().getValue(
                     R.dimen.config_screen_magnification_scaling_threshold,
                     scaleValue, false);
+            mContext = context;
             mScalingThreshold = scaleValue.getFloat();
             mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain());
             mScaleGestureDetector.setQuickScaleEnabled(false);
@@ -351,12 +373,59 @@
             }
         }
 
+
+        void prepareForState() {
+            checkShouldDetectPassPersistedScale();
+        }
+
+        private void checkShouldDetectPassPersistedScale() {
+            if (mDetectingPassPersistedScale) {
+                return;
+            }
+
+            final float currentScale =
+                    mFullScreenMagnificationController.getScale(mDisplayId);
+            final float persistedScale =
+                    mFullScreenMagnificationController.getPersistedScale(mDisplayId);
+
+            mDetectingPassPersistedScale =
+                    (abs(currentScale - persistedScale) / persistedScale)
+                            >= CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+        }
+
         public void persistScaleAndTransitionTo(State state) {
             mFullScreenMagnificationController.persistScale(mDisplayId);
             clear();
             transitionTo(state);
         }
 
+        @VisibleForTesting
+        void setScaleAndClearIfNeeded(float scale, float pivotX, float pivotY) {
+            if (mDetectingPassPersistedScale) {
+                final float persistedScale =
+                        mFullScreenMagnificationController.getPersistedScale(mDisplayId);
+                // If the scale passes the persisted scale during panning, perform a vibration
+                // feedback to user. Also, call {@link clear} to create a buffer zone so that
+                // user needs to panning more than {@link mScalingThreshold} to change scale again.
+                if (abs(scale - persistedScale) / persistedScale
+                        < PASSING_PERSISTED_SCALE_THRESHOLD) {
+                    scale = persistedScale;
+                    final Vibrator vibrator = mContext.getSystemService(Vibrator.class);
+                    if (vibrator != null) {
+                        vibrator.vibrate(
+                                VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK));
+                    }
+                    clear();
+                }
+            }
+
+            if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x");
+            mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
+                    AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+
+            checkShouldDetectPassPersistedScale();
+        }
+
         @Override
         public boolean onScroll(MotionEvent first, MotionEvent second,
                 float distanceX, float distanceY) {
@@ -402,11 +471,7 @@
                 scale = targetScale;
             }
 
-            final float pivotX = detector.getFocusX();
-            final float pivotY = detector.getFocusY();
-            if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x");
-            mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
-                    AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+            setScaleAndClearIfNeeded(scale, detector.getFocusX(), detector.getFocusY());
             return /* handled: */ true;
         }
 
@@ -424,6 +489,7 @@
         public void clear() {
             mInitialScaleFactor = -1;
             mScaling = false;
+            mDetectingPassPersistedScale = false;
         }
 
         @Override
diff --git a/services/api/current.txt b/services/api/current.txt
index 329dbdf..b55166c 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -43,6 +43,8 @@
     method @Deprecated public boolean bindSdkSandboxService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, @NonNull String, int) throws android.os.RemoteException;
     method public boolean canStartForegroundService(int, int, @NonNull String);
     method public void killSdkSandboxClientAppProcess(@NonNull android.os.IBinder);
+    method @Nullable public android.content.ComponentName startSdkSandboxService(@NonNull android.content.Intent, int, @NonNull String, @NonNull String) throws android.os.RemoteException;
+    method public boolean stopSdkSandboxService(@NonNull android.content.Intent, int, @NonNull String, @NonNull String);
   }
 
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index b4dcf43..f650560 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -51,6 +51,10 @@
 import android.content.pm.ActivityInfo;
 import android.graphics.PointF;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.input.VirtualDpadConfig;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualKeyboardConfig;
@@ -82,6 +86,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.server.LocalServices;
 import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
 import com.android.server.companion.virtual.audio.VirtualAudioController;
 
@@ -109,21 +114,29 @@
 
     private final Context mContext;
     private final AssociationInfo mAssociationInfo;
+    private final VirtualDeviceManagerService mService;
     private final PendingTrampolineCallback mPendingTrampolineCallback;
     private final int mOwnerUid;
     private final int mDeviceId;
+    // Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
+    // Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
+    // 1. After display is created the window manager calls into VDM during construction
+    //   of display specific context to fetch device id corresponding to the display.
+    //   mVirtualDeviceLock will be held while this is done.
+    // 2. InputController interactions result in calls to DisplayManager (to set IME,
+    //    possibly more indirect calls), and those attempt to lock GlobalWindowManagerLock which
+    //    creates lock inversion.
     private final InputController mInputController;
     private final SensorController mSensorController;
     private final CameraAccessController mCameraAccessController;
     private VirtualAudioController mVirtualAudioController;
-    @VisibleForTesting
-    final ArraySet<Integer> mVirtualDisplayIds = new ArraySet<>();
-    private final OnDeviceCloseListener mOnDeviceCloseListener;
     private final IBinder mAppToken;
     private final VirtualDeviceParams mParams;
-    private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
+    @GuardedBy("mVirtualDeviceLock")
+    private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
     private final IVirtualDeviceActivityListener mActivityListener;
     private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
+    private final DisplayManagerGlobal mDisplayManager;
     @GuardedBy("mVirtualDeviceLock")
     private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
     @NonNull
@@ -174,21 +187,14 @@
         };
     }
 
-    /**
-     * A mapping from the virtual display ID to its corresponding
-     * {@link GenericWindowPolicyController}.
-     */
-    private final SparseArray<GenericWindowPolicyController> mWindowPolicyControllers =
-            new SparseArray<>();
-
     VirtualDeviceImpl(
             Context context,
             AssociationInfo associationInfo,
+            VirtualDeviceManagerService service,
             IBinder token,
             int ownerUid,
             int deviceId,
             CameraAccessController cameraAccessController,
-            OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             IVirtualDeviceSoundEffectListener soundEffectListener,
@@ -197,40 +203,43 @@
         this(
                 context,
                 associationInfo,
+                service,
                 token,
                 ownerUid,
                 deviceId,
                 /* inputController= */ null,
                 /* sensorController= */ null,
                 cameraAccessController,
-                onDeviceCloseListener,
                 pendingTrampolineCallback,
                 activityListener,
                 soundEffectListener,
                 runningAppsChangedCallback,
-                params);
+                params,
+                DisplayManagerGlobal.getInstance());
     }
 
     @VisibleForTesting
     VirtualDeviceImpl(
             Context context,
             AssociationInfo associationInfo,
+            VirtualDeviceManagerService service,
             IBinder token,
             int ownerUid,
             int deviceId,
             InputController inputController,
             SensorController sensorController,
             CameraAccessController cameraAccessController,
-            OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             IVirtualDeviceSoundEffectListener soundEffectListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
-            VirtualDeviceParams params) {
+            VirtualDeviceParams params,
+            DisplayManagerGlobal displayManager) {
         super(PermissionEnforcer.fromContext(context));
         UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid);
         mContext = context.createContextAsUser(ownerUserHandle, 0);
         mAssociationInfo = associationInfo;
+        mService = service;
         mPendingTrampolineCallback = pendingTrampolineCallback;
         mActivityListener = activityListener;
         mSoundEffectListener = soundEffectListener;
@@ -239,6 +248,7 @@
         mDeviceId = deviceId;
         mAppToken = token;
         mParams = params;
+        mDisplayManager = displayManager;
         if (inputController == null) {
             mInputController = new InputController(
                     mVirtualDeviceLock,
@@ -259,7 +269,6 @@
         }
         mCameraAccessController = cameraAccessController;
         mCameraAccessController.startObservingIfNeeded();
-        mOnDeviceCloseListener = onDeviceCloseListener;
         try {
             token.linkToDeath(this, 0);
         } catch (RemoteException e) {
@@ -331,9 +340,11 @@
     @Override // Binder call
     public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
             ResultReceiver resultReceiver) {
-        if (!mVirtualDisplayIds.contains(displayId)) {
-            throw new SecurityException("Display ID " + displayId
-                    + " not found for this virtual device");
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplays.contains(displayId)) {
+                throw new SecurityException("Display ID " + displayId
+                        + " not found for this virtual device");
+            }
         }
         if (pendingIntent.isActivity()) {
             try {
@@ -383,24 +394,34 @@
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void close() {
         super.close_enforcePermission();
+        // Remove about-to-be-closed virtual device from the service before butchering it.
+        mService.removeVirtualDevice(mDeviceId);
+
+        VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
         synchronized (mVirtualDeviceLock) {
-            if (!mPerDisplayWakelocks.isEmpty()) {
-                mPerDisplayWakelocks.forEach((displayId, wakeLock) -> {
-                    Slog.w(TAG, "VirtualDisplay " + displayId + " owned by UID " + mOwnerUid
-                            + " was not properly released");
-                    wakeLock.release();
-                });
-                mPerDisplayWakelocks.clear();
-            }
             if (mVirtualAudioController != null) {
                 mVirtualAudioController.stopListening();
                 mVirtualAudioController = null;
             }
             mLocaleList = null;
+            virtualDisplaysToBeReleased = new VirtualDisplayWrapper[mVirtualDisplays.size()];
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i);
+            }
+            mVirtualDisplays.clear();
             mVirtualSensorList = null;
             mVirtualSensors.clear();
         }
-        mOnDeviceCloseListener.onClose(mDeviceId);
+        // Destroy the display outside locked section.
+        for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) {
+            mDisplayManager.releaseVirtualDisplay(virtualDisplayWrapper.getToken());
+            // The releaseVirtualDisplay call above won't trigger
+            // VirtualDeviceImpl.onVirtualDisplayRemoved callback because we already removed the
+            // virtual device from the service - we release the other display-tied resources here
+            // with the guarantee it will be done exactly once.
+            releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+        }
+
         mAppToken.unlinkToDeath(this, 0);
         mCameraAccessController.stopObservingIfNeeded();
 
@@ -429,11 +450,6 @@
         return mVirtualAudioController;
     }
 
-    @VisibleForTesting
-    SparseArray<GenericWindowPolicyController> getWindowPolicyControllersForTesting() {
-        return mWindowPolicyControllers;
-    }
-
     @Override // Binder call
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void onAudioSessionStarting(int displayId,
@@ -441,7 +457,7 @@
             @Nullable IAudioConfigChangedCallback configChangedCallback) {
         super.onAudioSessionStarting_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(displayId)) {
+            if (!mVirtualDisplays.contains(displayId)) {
                 throw new SecurityException(
                         "Cannot start audio session for a display not associated with this virtual "
                                 + "device");
@@ -449,7 +465,8 @@
 
             if (mVirtualAudioController == null) {
                 mVirtualAudioController = new VirtualAudioController(mContext);
-                GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
+                GenericWindowPolicyController gwpc = mVirtualDisplays.get(
+                        displayId).getWindowPolicyController();
                 mVirtualAudioController.startListening(gwpc, routingCallback,
                         configChangedCallback);
             }
@@ -473,7 +490,7 @@
     public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualDpad_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual dpad for a display not associated with "
                                 + "this virtual device");
@@ -493,7 +510,7 @@
     public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualKeyboard_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual keyboard for a display not associated with "
                                 + "this virtual device");
@@ -515,7 +532,7 @@
     public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualMouse_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual mouse for a display not associated with this "
                                 + "virtual device");
@@ -536,7 +553,7 @@
             @NonNull IBinder deviceToken) {
         super.createVirtualTouchscreen_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual touchscreen for a display not associated with "
                                 + "this virtual device");
@@ -566,7 +583,7 @@
             @NonNull IBinder deviceToken) {
         super.createVirtualNavigationTouchpad_enforcePermission();
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual navigation touchpad for a display not associated "
                                 + "with this virtual device");
@@ -704,7 +721,8 @@
         try {
             synchronized (mVirtualDeviceLock) {
                 mDefaultShowPointerIcon = showPointerIcon;
-                for (int displayId : mVirtualDisplayIds) {
+                for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                    final int displayId = mVirtualDisplays.keyAt(i);
                     mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
                 }
             }
@@ -795,8 +813,8 @@
         fout.println("    mParams: " + mParams);
         fout.println("    mVirtualDisplayIds: ");
         synchronized (mVirtualDeviceLock) {
-            for (int id : mVirtualDisplayIds) {
-                fout.println("      " + id);
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                fout.println("      " + mVirtualDisplays.keyAt(i));
             }
             fout.println("    mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
         }
@@ -804,61 +822,75 @@
         mSensorController.dump(fout);
     }
 
-    GenericWindowPolicyController createWindowPolicyController(
+    private GenericWindowPolicyController createWindowPolicyController(
             @NonNull List<String> displayCategories) {
-        synchronized (mVirtualDeviceLock) {
-            final GenericWindowPolicyController gwpc =
-                    new GenericWindowPolicyController(FLAG_SECURE,
-                            SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
-                            getAllowedUserHandles(),
-                            mParams.getAllowedCrossTaskNavigations(),
-                            mParams.getBlockedCrossTaskNavigations(),
-                            mParams.getAllowedActivities(),
-                            mParams.getBlockedActivities(),
-                            mParams.getDefaultActivityPolicy(),
-                            createListenerAdapter(),
-                            this::onEnteringPipBlocked,
-                            this::onActivityBlocked,
-                            this::onSecureWindowShown,
-                            this::shouldInterceptIntent,
-                            displayCategories,
-                            mParams.getDefaultRecentsPolicy());
-            gwpc.registerRunningAppsChangedListener(/* listener= */ this);
-            return gwpc;
-        }
+        final GenericWindowPolicyController gwpc =
+                new GenericWindowPolicyController(FLAG_SECURE,
+                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        getAllowedUserHandles(),
+                        mParams.getAllowedCrossTaskNavigations(),
+                        mParams.getBlockedCrossTaskNavigations(),
+                        mParams.getAllowedActivities(),
+                        mParams.getBlockedActivities(),
+                        mParams.getDefaultActivityPolicy(),
+                        createListenerAdapter(),
+                        this::onEnteringPipBlocked,
+                        this::onActivityBlocked,
+                        this::onSecureWindowShown,
+                        this::shouldInterceptIntent,
+                        displayCategories,
+                        mParams.getDefaultRecentsPolicy());
+        gwpc.registerRunningAppsChangedListener(/* listener= */ this);
+        return gwpc;
     }
 
-    void onVirtualDisplayCreatedLocked(GenericWindowPolicyController gwpc, int displayId) {
+    int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
+            @NonNull IVirtualDisplayCallback callback, String packageName) {
+        GenericWindowPolicyController gwpc = createWindowPolicyController(
+                virtualDisplayConfig.getDisplayCategories());
+        DisplayManagerInternal displayManager = LocalServices.getService(
+                DisplayManagerInternal.class);
+        int displayId;
+        displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback,
+                this, gwpc, packageName);
+        gwpc.setDisplayId(displayId);
+
         synchronized (mVirtualDeviceLock) {
-            if (displayId == Display.INVALID_DISPLAY) {
-                return;
-            }
-            if (mVirtualDisplayIds.contains(displayId)) {
+            if (mVirtualDisplays.contains(displayId)) {
+                gwpc.unregisterRunningAppsChangedListener(this);
                 throw new IllegalStateException(
                         "Virtual device already has a virtual display with ID " + displayId);
             }
-            mVirtualDisplayIds.add(displayId);
 
-            gwpc.setDisplayId(displayId);
-            mWindowPolicyControllers.put(displayId, gwpc);
+            PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId);
+            mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock));
+        }
 
+        final long token = Binder.clearCallingIdentity();
+        try {
             mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
             mInputController.setPointerAcceleration(1f, displayId);
             mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
                     displayId);
             mInputController.setLocalIme(displayId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
 
+        return displayId;
+    }
 
-            if (mPerDisplayWakelocks.containsKey(displayId)) {
-                Slog.e(TAG, "Not creating wakelock for displayId " + displayId);
-                return;
-            }
+    private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
+        final long token = Binder.clearCallingIdentity();
+        try {
             PowerManager powerManager = mContext.getSystemService(PowerManager.class);
             PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
                     PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
                     TAG + ":" + displayId, displayId);
-            mPerDisplayWakelocks.put(displayId, wakeLock);
             wakeLock.acquire();
+            return wakeLock;
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
@@ -872,8 +904,10 @@
     }
 
     private void onSecureWindowShown(int displayId, int uid) {
-        if (!mVirtualDisplayIds.contains(displayId)) {
-            return;
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplays.contains(displayId)) {
+                return;
+            }
         }
 
         // If a virtual display isn't secure, the screen can't be captured. Show a warning toast
@@ -888,55 +922,102 @@
 
     private ArraySet<UserHandle> getAllowedUserHandles() {
         ArraySet<UserHandle> result = new ArraySet<>();
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        UserManager userManager = mContext.getSystemService(UserManager.class);
-        for (UserHandle profile : userManager.getAllProfiles()) {
-            int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier());
-            if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
-                    || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
-                result.add(profile);
-            } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
-                if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+            UserManager userManager = mContext.getSystemService(UserManager.class);
+            for (UserHandle profile : userManager.getAllProfiles()) {
+                int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(
+                        profile.getIdentifier());
+                if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
+                        || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
                     result.add(profile);
+                } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
+                    if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+                        result.add(profile);
+                    }
                 }
             }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
         return result;
     }
 
-    void onVirtualDisplayRemovedLocked(int displayId) {
+
+    void onVirtualDisplayRemoved(int displayId) {
+        /* This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released
+         * by DisplayManager (most probably caused by someone calling VirtualDisplay.close()).
+         * At this point, the display is already released, but we still need to release the
+         * corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding
+         * WindowPolicyController.
+         *
+         * Note that when the display is destroyed during VirtualDeviceImpl.close() call,
+         * this callback won't be invoked because the display is removed from
+         * VirtualDeviceManagerService before any resources are released.
+         */
+        VirtualDisplayWrapper virtualDisplayWrapper;
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplayIds.contains(displayId)) {
-                throw new IllegalStateException(
-                        "Virtual device doesn't have a virtual display with ID " + displayId);
-            }
-            PowerManager.WakeLock wakeLock = mPerDisplayWakelocks.get(displayId);
-            if (wakeLock != null) {
-                wakeLock.release();
-                mPerDisplayWakelocks.remove(displayId);
-            }
-            GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
-            if (gwpc != null) {
-                gwpc.unregisterRunningAppsChangedListener(/* listener= */ this);
-            }
-            mVirtualDisplayIds.remove(displayId);
-            mWindowPolicyControllers.remove(displayId);
+            virtualDisplayWrapper = mVirtualDisplays.removeReturnOld(displayId);
         }
+
+        if (virtualDisplayWrapper == null) {
+            throw new IllegalStateException(
+                    "Virtual device doesn't have a virtual display with ID " + displayId);
+        }
+
+        releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+
+    }
+
+    /**
+     * Release resources tied to virtual display owned by this VirtualDevice instance.
+     *
+     * Note that this method won't release the virtual display itself.
+     *
+     * @param virtualDisplayWrapper - VirtualDisplayWrapper to release resources for.
+     */
+    private void releaseOwnedVirtualDisplayResources(VirtualDisplayWrapper virtualDisplayWrapper) {
+        virtualDisplayWrapper.getWakeLock().release();
+        virtualDisplayWrapper.getWindowPolicyController().unregisterRunningAppsChangedListener(
+                this);
     }
 
     int getOwnerUid() {
         return mOwnerUid;
     }
 
+    ArraySet<Integer> getDisplayIds() {
+        synchronized (mVirtualDeviceLock) {
+            final int size = mVirtualDisplays.size();
+            ArraySet<Integer> arraySet = new ArraySet<>(size);
+            for (int i = 0; i < size; i++) {
+                arraySet.append(mVirtualDisplays.keyAt(i));
+            }
+            return arraySet;
+        }
+    }
+
+    @VisibleForTesting
+    GenericWindowPolicyController getDisplayWindowPolicyControllerForTest(int displayId) {
+        VirtualDisplayWrapper virtualDisplayWrapper;
+        synchronized (mVirtualDeviceLock) {
+            virtualDisplayWrapper = mVirtualDisplays.get(displayId);
+        }
+        return virtualDisplayWrapper != null ? virtualDisplayWrapper.getWindowPolicyController()
+                : null;
+    }
+
     /**
      * Returns true if an app with the given {@code uid} is currently running on this virtual
      * device.
      */
     boolean isAppRunningOnVirtualDevice(int uid) {
-        final int size = mWindowPolicyControllers.size();
-        for (int i = 0; i < size; i++) {
-            if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
-                return true;
+        synchronized (mVirtualDeviceLock) {
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
+                    return true;
+                }
             }
         }
         return false;
@@ -957,11 +1038,9 @@
             Looper looper) {
         synchronized (mVirtualDeviceLock) {
             DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-            final int size = mWindowPolicyControllers.size();
-            for (int i = 0; i < size; i++) {
-                if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
-                    int displayId = mWindowPolicyControllers.keyAt(i);
-                    Display display = displayManager.getDisplay(displayId);
+            for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
+                    Display display = displayManager.getDisplay(mVirtualDisplays.keyAt(i));
                     if (display != null && display.isValid()) {
                         Toast.makeText(mContext.createDisplayContext(display), looper, text,
                                 duration).show();
@@ -972,7 +1051,9 @@
     }
 
     boolean isDisplayOwnedByVirtualDevice(int displayId) {
-        return mVirtualDisplayIds.contains(displayId);
+        synchronized (mVirtualDeviceLock) {
+            return mVirtualDisplays.contains(displayId);
+        }
     }
 
     void onEnteringPipBlocked(int uid) {
@@ -1016,10 +1097,6 @@
         }
     }
 
-    interface OnDeviceCloseListener {
-        void onClose(int deviceId);
-    }
-
     interface PendingTrampolineCallback {
         /**
          * Called when the callback should start waiting for the given pending trampoline.
@@ -1073,4 +1150,31 @@
                     + ", displayId=" + mDisplayId + "}";
         }
     }
+
+    /** Data class wrapping resources tied to single virtual display. */
+    private static final class VirtualDisplayWrapper {
+        private final IVirtualDisplayCallback mToken;
+        private final GenericWindowPolicyController mWindowPolicyController;
+        private final PowerManager.WakeLock mWakeLock;
+
+        VirtualDisplayWrapper(@NonNull IVirtualDisplayCallback token,
+                @NonNull GenericWindowPolicyController windowPolicyController,
+                @NonNull PowerManager.WakeLock wakeLock) {
+            mToken = Objects.requireNonNull(token);
+            mWindowPolicyController = Objects.requireNonNull(windowPolicyController);
+            mWakeLock = Objects.requireNonNull(wakeLock);
+        }
+
+        GenericWindowPolicyController getWindowPolicyController() {
+            return mWindowPolicyController;
+        }
+
+        PowerManager.WakeLock getWakeLock() {
+            return mWakeLock;
+        }
+
+        IVirtualDisplayCallback getToken() {
+            return mToken;
+        }
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 9bb05a6..ed5b858 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -37,7 +37,6 @@
 import android.companion.virtual.sensor.VirtualSensor;
 import android.content.Context;
 import android.content.Intent;
-import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
 import android.os.Binder;
@@ -108,26 +107,26 @@
     private final ActivityInterceptorCallback mActivityInterceptorCallback =
             new ActivityInterceptorCallback() {
 
-        @Nullable
-        @Override
-        public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
-                ActivityInterceptorInfo info) {
-            if (info.getCallingPackage() == null) {
-                return null;
-            }
-            PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage());
-            if (pt == null) {
-                return null;
-            }
-            pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
-            ActivityOptions options = info.getCheckedOptions();
-            if (options == null) {
-                options = ActivityOptions.makeBasic();
-            }
-            return new ActivityInterceptResult(
-                    info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId));
-        }
-    };
+                @Nullable
+                @Override
+                public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
+                        ActivityInterceptorInfo info) {
+                    if (info.getCallingPackage() == null) {
+                        return null;
+                    }
+                    PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage());
+                    if (pt == null) {
+                        return null;
+                    }
+                    pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
+                    ActivityOptions options = info.getCheckedOptions();
+                    if (options == null) {
+                        options = ActivityOptions.makeBasic();
+                    }
+                    return new ActivityInterceptResult(
+                            info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId));
+                }
+            };
 
     @Override
     public void onStart() {
@@ -146,8 +145,8 @@
                 CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName();
                 mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
                         getContext().getString(
-                            com.android.internal.R.string.vdm_camera_access_denied,
-                            deviceName),
+                                com.android.internal.R.string.vdm_camera_access_denied,
+                                deviceName),
                         Toast.LENGTH_LONG, Looper.myLooper());
             }
         }
@@ -193,34 +192,46 @@
         }
     }
 
-    @VisibleForTesting
     void removeVirtualDevice(int deviceId) {
         synchronized (mVirtualDeviceManagerLock) {
             mAppsOnVirtualDevices.remove(deviceId);
             mVirtualDevices.remove(deviceId);
         }
+
+        Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+        i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
+        i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
 
         private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback =
                 new VirtualDeviceImpl.PendingTrampolineCallback() {
-            @Override
-            public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
-                PendingTrampoline existing = mPendingTrampolines.put(
-                        pendingTrampoline.mPendingIntent.getCreatorPackage(),
-                        pendingTrampoline);
-                if (existing != null) {
-                    existing.mResultReceiver.send(
-                            VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
-                }
-            }
+                    @Override
+                    public void startWaitingForPendingTrampoline(
+                            PendingTrampoline pendingTrampoline) {
+                        PendingTrampoline existing = mPendingTrampolines.put(
+                                pendingTrampoline.mPendingIntent.getCreatorPackage(),
+                                pendingTrampoline);
+                        if (existing != null) {
+                            existing.mResultReceiver.send(
+                                    VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
+                        }
+                    }
 
-            @Override
-            public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
-                mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
-            }
-        };
+                    @Override
+                    public void stopWaitingForPendingTrampoline(
+                            PendingTrampoline pendingTrampoline) {
+                        mPendingTrampolines.remove(
+                                pendingTrampoline.mPendingIntent.getCreatorPackage());
+                    }
+                };
 
         @Override // Binder call
         public IVirtualDevice createVirtualDevice(
@@ -251,8 +262,9 @@
                 final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
                         runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
                 VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
-                        associationInfo, token, callingUid, deviceId, cameraAccessController,
-                        this::onDeviceClosed, mPendingTrampolineCallback, activityListener,
+                        associationInfo, VirtualDeviceManagerService.this, token, callingUid,
+                        deviceId, cameraAccessController,
+                        mPendingTrampolineCallback, activityListener,
                         soundEffectListener, runningAppsChangedCallback, params);
                 mVirtualDevices.put(deviceId, virtualDevice);
                 return virtualDevice;
@@ -281,26 +293,9 @@
                         "uid " + callingUid
                                 + " is not the owner of the supplied VirtualDevice");
             }
-            GenericWindowPolicyController gwpc;
-            final long token = Binder.clearCallingIdentity();
-            try {
-                gwpc = virtualDeviceImpl.createWindowPolicyController(
-                    virtualDisplayConfig.getDisplayCategories());
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
 
-            DisplayManagerInternal displayManager = getLocalService(
-                    DisplayManagerInternal.class);
-            int displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback,
-                    virtualDevice, gwpc, packageName);
-
-            final long tokenTwo = Binder.clearCallingIdentity();
-            try {
-                virtualDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, displayId);
-            } finally {
-                Binder.restoreCallingIdentity(tokenTwo);
-            }
+            int displayId = virtualDeviceImpl.createVirtualDisplay(virtualDisplayConfig, callback,
+                    packageName);
             mLocalService.onVirtualDisplayCreated(displayId);
             return displayId;
         }
@@ -412,19 +407,6 @@
             return null;
         }
 
-        private void onDeviceClosed(int deviceId) {
-            removeVirtualDevice(deviceId);
-            Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
-            i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
-            i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                getContext().sendBroadcastAsUser(i, UserHandle.ALL);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
         @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
@@ -512,9 +494,14 @@
         @Override
         public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) {
             final VirtualDisplayListener[] listeners;
+            VirtualDeviceImpl virtualDeviceImpl;
             synchronized (mVirtualDeviceManagerLock) {
-                ((VirtualDeviceImpl) virtualDevice).onVirtualDisplayRemovedLocked(displayId);
                 listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]);
+                virtualDeviceImpl = mVirtualDevices.get(
+                        ((VirtualDeviceImpl) virtualDevice).getDeviceId());
+            }
+            if (virtualDeviceImpl != null) {
+                virtualDeviceImpl.onVirtualDisplayRemoved(displayId);
             }
             mHandler.post(() -> {
                 for (VirtualDisplayListener listener : listeners) {
@@ -599,16 +586,11 @@
 
         @Override
         public @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId) {
+            VirtualDeviceImpl virtualDevice;
             synchronized (mVirtualDeviceManagerLock) {
-                int size = mVirtualDevices.size();
-                for (int i = 0; i < size; i++) {
-                    VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
-                    if (device.getDeviceId() == deviceId) {
-                        return new ArraySet<>(device.mVirtualDisplayIds);
-                    }
-                }
+                virtualDevice = mVirtualDevices.get(deviceId);
             }
-            return new ArraySet<>();
+            return virtualDevice == null ? new ArraySet<>() : virtualDevice.getDisplayIds();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 669dcfc..92e322f 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -153,6 +153,8 @@
     private int mLastMaxChargingCurrent;
     private int mLastMaxChargingVoltage;
     private int mLastChargeCounter;
+    private int mLastBatteryCycleCount;
+    private int mLastCharingState;
 
     private int mSequence = 1;
 
@@ -523,7 +525,9 @@
                         || mHealthInfo.maxChargingCurrentMicroamps != mLastMaxChargingCurrent
                         || mHealthInfo.maxChargingVoltageMicrovolts != mLastMaxChargingVoltage
                         || mHealthInfo.batteryChargeCounterUah != mLastChargeCounter
-                        || mInvalidCharger != mLastInvalidCharger)) {
+                        || mInvalidCharger != mLastInvalidCharger
+                        || mHealthInfo.batteryCycleCount != mLastBatteryCycleCount
+                        || mHealthInfo.chargingState != mLastCharingState)) {
 
             if (mPlugType != mLastPlugType) {
                 if (mLastPlugType == BATTERY_PLUGGED_NONE) {
@@ -705,6 +709,8 @@
             mLastChargeCounter = mHealthInfo.batteryChargeCounterUah;
             mLastBatteryLevelCritical = mBatteryLevelCritical;
             mLastInvalidCharger = mInvalidCharger;
+            mLastBatteryCycleCount = mHealthInfo.batteryCycleCount;
+            mLastCharingState = mHealthInfo.chargingState;
         }
     }
 
@@ -736,6 +742,8 @@
                 BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE,
                 mHealthInfo.maxChargingVoltageMicrovolts);
         intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounterUah);
+        intent.putExtra(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount);
+        intent.putExtra(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState);
         if (DEBUG) {
             Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
                     + ", info:" + mHealthInfo.toString());
@@ -760,6 +768,8 @@
         event.putInt(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperatureTenthsCelsius);
         event.putInt(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounterUah);
         event.putLong(BatteryManager.EXTRA_EVENT_TIMESTAMP, now);
+        event.putInt(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount);
+        event.putInt(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState);
 
         boolean queueWasEmpty = mBatteryLevelsEventQueue.isEmpty();
         mBatteryLevelsEventQueue.add(event);
@@ -1278,11 +1288,20 @@
         }
     }
 
-    // Reduced IBatteryPropertiesRegistrar that only implements getProperty for usage
-    // in BatteryManager.
+    // Reduced IBatteryPropertiesRegistrar that implements getProperty for usage
+    // in BatteryManager and enforce permissions.
     private final class BatteryPropertiesRegistrar extends IBatteryPropertiesRegistrar.Stub {
         @Override
         public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
+            switch (id) {
+                case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
+                case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
+                case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
+                case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH:
+                    mContext.enforceCallingPermission(
+                            android.Manifest.permission.BATTERY_STATS, null);
+                    break;
+            }
             return mHealthServiceWrapper.getProperty(id, prop);
         }
         @Override
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 2a46d86..3f1ad3a 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -16,12 +16,18 @@
 
 package com.android.server;
 
+import static android.content.IntentFilter.BLOCK_NULL_ACTION_INTENTS;
+
+import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.Uri;
+import android.os.Binder;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.FastImmutableArraySet;
@@ -34,6 +40,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.FastPrintWriter;
+import com.android.server.am.ActivityManagerUtils;
 import com.android.server.pm.Computer;
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 
@@ -81,7 +88,7 @@
      * Returns whether an intent matches the IntentFilter with a pre-resolved type.
      */
     public static boolean intentMatchesFilter(
-            IntentFilter filter, Intent intent, String resolvedType) {
+            IntentFilter filter, Intent intent, String resolvedType, boolean blockNullAction) {
         final boolean debug = localLOGV
                 || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
 
@@ -95,7 +102,8 @@
         }
 
         final int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(),
-                intent.getData(), intent.getCategories(), TAG);
+                intent.getData(), intent.getCategories(), TAG, /* supportWildcards */ false,
+                blockNullAction, null, null);
 
         if (match >= 0) {
             if (debug) {
@@ -350,14 +358,32 @@
         return Collections.unmodifiableSet(mFilters);
     }
 
+    private boolean blockNullAction(Computer computer, Intent intent,
+            String resolvedType, int callingUid, boolean debug) {
+        if (intent.getAction() == null) {
+            final boolean blockNullAction = UserHandle.isCore(callingUid)
+                    || computer.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS, callingUid);
+            ActivityManagerUtils.logUnsafeIntentEvent(
+                    UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH,
+                    callingUid, intent, resolvedType, blockNullAction);
+            if (blockNullAction) {
+                if (debug) Slog.v(TAG, "Skip matching filters: action is null");
+                return true;
+            }
+        }
+        return false;
+    }
+
     public List<R> queryIntentFromList(@NonNull Computer computer, Intent intent,
-            String resolvedType, boolean defaultOnly, ArrayList<F[]> listCut, int userId,
-            long customFlags) {
+            String resolvedType, boolean defaultOnly, ArrayList<F[]> listCut,
+            int callingUid, @UserIdInt int userId, long customFlags) {
         ArrayList<R> resultList = new ArrayList<R>();
 
         final boolean debug = localLOGV ||
                 ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
 
+        if (blockNullAction(computer, intent, resolvedType, callingUid, debug)) return resultList;
+
         FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
         final String scheme = intent.getScheme();
         int N = listCut.size();
@@ -365,18 +391,26 @@
             buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType, scheme,
                     listCut.get(i), resultList, userId, customFlags);
         }
-        filterResults(resultList);
+        filterResults(computer, intent, resultList);
         sortResults(resultList);
         return resultList;
     }
 
-    public List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+    public final List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
             String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
-        return queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, 0);
+        return queryIntent(snapshot, intent, resolvedType, defaultOnly,
+                Binder.getCallingUid(), userId, 0);
+    }
+
+    public List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+            String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId) {
+        return queryIntent(snapshot, intent, resolvedType, defaultOnly, callingUid, userId, 0);
     }
 
     protected final List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
-            String resolvedType, boolean defaultOnly, @UserIdInt int userId, long customFlags) {
+            String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId,
+            long customFlags) {
+        final Computer computer = (Computer) snapshot;
         String scheme = intent.getScheme();
 
         ArrayList<R> finalList = new ArrayList<R>();
@@ -388,6 +422,8 @@
             TAG, "Resolving type=" + resolvedType + " scheme=" + scheme
             + " defaultOnly=" + defaultOnly + " userId=" + userId + " of " + intent);
 
+        if (blockNullAction(computer, intent, resolvedType, callingUid, debug)) return finalList;
+
         F[] firstTypeCut = null;
         F[] secondTypeCut = null;
         F[] thirdTypeCut = null;
@@ -448,7 +484,6 @@
         }
 
         FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
-        Computer computer = (Computer) snapshot;
         if (firstTypeCut != null) {
             buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType,
                     scheme, firstTypeCut, finalList, userId, customFlags);
@@ -465,7 +500,7 @@
             buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType,
                     scheme, schemeCut, finalList, userId, customFlags);
         }
-        filterResults(finalList);
+        filterResults(computer, intent, finalList);
         sortResults(finalList);
 
         if (debug) {
@@ -534,7 +569,8 @@
     /**
      * Apply filtering to the results. This happens before the results are sorted.
      */
-    protected void filterResults(List<R> results) {
+    protected void filterResults(@NonNull Computer computer, @NonNull Intent intent,
+            List<R> results) {
     }
 
     protected void dumpFilter(PrintWriter out, String prefix, F filter) {
@@ -766,7 +802,11 @@
                 continue;
             }
 
-            match = intentFilter.match(action, resolvedType, scheme, data, categories, TAG);
+            match = intentFilter.match(action, resolvedType, scheme, data, categories, TAG,
+                    false /*supportWildcards*/,
+                    false /*blockNullAction: already handled*/,
+                    null /*ignoreActions*/,
+                    null /*extras*/);
             if (match >= 0) {
                 if (debug) Slog.v(TAG, "  Filter matched!  match=0x" +
                         Integer.toHexString(match) + " hasDefault="
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0252492..e0d1c1e 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2753,6 +2753,8 @@
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
 
         try {
+            int avgWriteAmount = 0;
+            int targetDirtyRatio = mTargetDirtyRatio;
             int latestWrite = mVold.getWriteAmount();
             if (latestWrite == -1) {
                 Slog.w(TAG, "Failed to get storage write record");
@@ -2765,11 +2767,12 @@
             // (first boot after OTA), We skip the smart idle maintenance
             if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
                 if (!refreshLifetimeConstraint() || !checkChargeStatus()) {
-                    return;
+                    Slog.i(TAG, "Turn off gc_urgent based on checking lifetime and charge status");
+                    targetDirtyRatio = 100;
+                } else {
+                    avgWriteAmount = getAverageWriteAmount();
                 }
 
-                int avgWriteAmount = getAverageWriteAmount();
-
                 Slog.i(TAG, "Set smart idle maintenance: " + "latest write amount: " +
                             latestWrite + ", average write amount: " + avgWriteAmount +
                             ", min segment threshold: " + mMinSegmentsThreshold +
@@ -2777,10 +2780,10 @@
                             ", segment reclaim weight: " + mSegmentReclaimWeight +
                             ", period(min): " + sSmartIdleMaintPeriod +
                             ", min gc sleep time(ms): " + mMinGCSleepTime +
-                            ", target dirty ratio: " + mTargetDirtyRatio);
+                            ", target dirty ratio: " + targetDirtyRatio);
                 mVold.setGCUrgentPace(avgWriteAmount, mMinSegmentsThreshold, mDirtyReclaimRate,
                                       mSegmentReclaimWeight, sSmartIdleMaintPeriod,
-                                      mMinGCSleepTime, mTargetDirtyRatio);
+                                      mMinGCSleepTime, targetDirtyRatio);
             } else {
                 Slog.i(TAG, "Skipping smart idle maintenance - block based checkpoint in progress");
             }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 3a93cb3..70304c5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -522,6 +522,7 @@
         final ArrayList<ServiceRecord> mStartingBackground = new ArrayList<>();
 
         final ArrayMap<String, ActiveForegroundApp> mActiveForegroundApps = new ArrayMap<>();
+        final ArrayList<String> mPendingRemoveForegroundApps = new ArrayList<>();
 
         boolean mActiveForegroundAppsChanged;
 
@@ -746,10 +747,13 @@
 
     ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
             int callingPid, int callingUid, boolean fgRequired, String callingPackage,
-            @Nullable String callingFeatureId, final int userId)
+            @Nullable String callingFeatureId, final int userId, boolean isSdkSandboxService,
+            int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String instanceName)
             throws TransactionTooLargeException {
         return startServiceLocked(caller, service, resolvedType, callingPid, callingUid, fgRequired,
-                callingPackage, callingFeatureId, userId, BackgroundStartPrivileges.NONE);
+                callingPackage, callingFeatureId, userId, BackgroundStartPrivileges.NONE,
+                isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
+                instanceName);
     }
 
     ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
@@ -757,6 +761,17 @@
             String callingPackage, @Nullable String callingFeatureId, final int userId,
             BackgroundStartPrivileges backgroundStartPrivileges)
             throws TransactionTooLargeException {
+        return startServiceLocked(caller, service, resolvedType, callingPid, callingUid, fgRequired,
+                callingPackage, callingFeatureId, userId, backgroundStartPrivileges,
+                false /* isSdkSandboxService */, INVALID_UID, null, null);
+    }
+
+    ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
+            int callingPid, int callingUid, boolean fgRequired,
+            String callingPackage, @Nullable String callingFeatureId, final int userId,
+            BackgroundStartPrivileges backgroundStartPrivileges, boolean isSdkSandboxService,
+            int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String instanceName)
+            throws TransactionTooLargeException {
         if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
                 + " type=" + resolvedType + " args=" + service.getExtras());
 
@@ -774,9 +789,9 @@
             callerFg = true;
         }
 
-        ServiceLookupResult res =
-            retrieveServiceLocked(service, null, resolvedType, callingPackage,
-                    callingPid, callingUid, userId, true, callerFg, false, false, false);
+        ServiceLookupResult res = retrieveServiceLocked(service, instanceName, isSdkSandboxService,
+                sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage,
+                callingPid, callingUid, userId, true, callerFg, false, false, null, false);
         if (res == null) {
             return null;
         }
@@ -800,16 +815,30 @@
             return null;
         }
 
+        // For the SDK sandbox, we start the service on behalf of the client app.
+        final int appUid = isSdkSandboxService ? sdkSandboxClientAppUid : r.appInfo.uid;
+        final String appPackageName =
+                isSdkSandboxService ? sdkSandboxClientAppPackage : r.packageName;
+        int appTargetSdkVersion = r.appInfo.targetSdkVersion;
+        if (isSdkSandboxService) {
+            try {
+                appTargetSdkVersion = AppGlobals.getPackageManager().getApplicationInfo(
+                        appPackageName, ActivityManagerService.STOCK_PM_FLAGS,
+                        userId).targetSdkVersion;
+            } catch (RemoteException ignored) {
+            }
+        }
+
         // If we're starting indirectly (e.g. from PendingIntent), figure out whether
         // we're launching into an app in a background state.  This keys off of the same
         // idleness state tracking as e.g. O+ background service start policy.
-        final boolean bgLaunch = !mAm.isUidActiveLOSP(r.appInfo.uid);
+        final boolean bgLaunch = !mAm.isUidActiveLOSP(appUid);
 
         // If the app has strict background restrictions, we treat any bg service
         // start analogously to the legacy-app forced-restrictions case, regardless
         // of its target SDK version.
         boolean forcedStandby = false;
-        if (bgLaunch && appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
+        if (bgLaunch && appRestrictedAnyInBackground(appUid, appPackageName)) {
             if (DEBUG_FOREGROUND_SERVICE) {
                 Slog.d(TAG, "Forcing bg-only service start only for " + r.shortInstanceName
                         + " : bgLaunch=" + bgLaunch + " callerFg=" + callerFg);
@@ -839,7 +868,7 @@
         boolean forceSilentAbort = false;
         if (fgRequired) {
             final int mode = mAm.getAppOpsManager().checkOpNoThrow(
-                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
+                    AppOpsManager.OP_START_FOREGROUND, appUid, appPackageName);
             switch (mode) {
                 case AppOpsManager.MODE_ALLOWED:
                 case AppOpsManager.MODE_DEFAULT:
@@ -861,12 +890,12 @@
         }
 
         // If this isn't a direct-to-foreground start, check our ability to kick off an
-        // arbitrary service
+        // arbitrary service.
         if (forcedStandby || (!r.startRequested && !fgRequired)) {
             // Before going further -- if this app is not allowed to start services in the
             // background, then at this point we aren't going to let it period.
-            final int allowed = mAm.getAppStartModeLOSP(r.appInfo.uid, r.packageName,
-                    r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
+            final int allowed = mAm.getAppStartModeLOSP(appUid, appPackageName, appTargetSdkVersion,
+                    callingPid, false, false, forcedStandby);
             if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                 Slog.w(TAG, "Background start not allowed: service "
                         + service + " to " + r.shortInstanceName
@@ -890,7 +919,7 @@
                 }
                 // This app knows it is in the new model where this operation is not
                 // allowed, so tell it what has happened.
-                UidRecord uidRec = mAm.mProcessList.getUidRecordLOSP(r.appInfo.uid);
+                UidRecord uidRec = mAm.mProcessList.getUidRecordLOSP(appUid);
                 return new ComponentName("?", "app is in background uid " + uidRec);
             }
         }
@@ -899,10 +928,10 @@
         // an ordinary startService() or a startForegroundService().  Now, only require that
         // the app follow through on the startForegroundService() -> startForeground()
         // contract if it actually targets O+.
-        if (r.appInfo.targetSdkVersion < Build.VERSION_CODES.O && fgRequired) {
+        if (appTargetSdkVersion < Build.VERSION_CODES.O && fgRequired) {
             if (DEBUG_BACKGROUND_CHECK || DEBUG_FOREGROUND_SERVICE) {
                 Slog.i(TAG, "startForegroundService() but host targets "
-                        + r.appInfo.targetSdkVersion + " - not requiring startForeground()");
+                        + appTargetSdkVersion + " - not requiring startForeground()");
             }
             fgRequired = false;
         }
@@ -1376,7 +1405,8 @@
     }
 
     int stopServiceLocked(IApplicationThread caller, Intent service,
-            String resolvedType, int userId) {
+            String resolvedType, int userId, boolean isSdkSandboxService,
+            int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String instanceName) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "stopService: " + service
                 + " type=" + resolvedType);
 
@@ -1389,9 +1419,10 @@
         }
 
         // If this service is active, make sure it is stopped.
-        ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, null,
+        ServiceLookupResult r = retrieveServiceLocked(service, instanceName, isSdkSandboxService,
+                sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, null,
                 Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false,
-                false);
+                null, false);
         if (r != null) {
             if (r.record != null) {
                 final long origId = Binder.clearCallingIdentity();
@@ -1657,13 +1688,14 @@
             if (smap != null) {
                 if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Updating foreground apps for user "
                         + smap.mUserId);
+                smap.mPendingRemoveForegroundApps.clear();
                 for (int i = smap.mActiveForegroundApps.size()-1; i >= 0; i--) {
                     ActiveForegroundApp aa = smap.mActiveForegroundApps.valueAt(i);
                     if (aa.mEndTime != 0) {
                         boolean canRemove = foregroundAppShownEnoughLocked(aa, now);
                         if (canRemove) {
                             // This was up for longer than the timeout, so just remove immediately.
-                            smap.mActiveForegroundApps.removeAt(i);
+                            smap.mPendingRemoveForegroundApps.add(smap.mActiveForegroundApps.keyAt(i));
                             smap.mActiveForegroundAppsChanged = true;
                             continue;
                         }
@@ -1688,6 +1720,9 @@
                         }
                     }
                 }
+                for(int i = smap.mPendingRemoveForegroundApps.size() - 1; i >= 0; i--) {
+                    smap.mActiveForegroundApps.remove(smap.mPendingRemoveForegroundApps.get(i));
+                }
                 smap.removeMessages(ServiceMap.MSG_UPDATE_FOREGROUND_APPS);
                 if (nextUpdateTime < Long.MAX_VALUE) {
                     if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Next update time in: "
@@ -2029,6 +2064,9 @@
                             foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
                     final boolean isOldTypeShortFgsAndTimedOut = r.shouldTriggerShortFgsTimeout();
 
+                    // If true, we skip the BFSL check.
+                    boolean bypassBfslCheck = false;
+
                     if (r.isForeground && (isOldTypeShortFgs || isNewTypeShortFgs)) {
                         if (DEBUG_SHORT_SERVICE) {
                             Slog.i(TAG_SERVICE, String.format(
@@ -2059,9 +2097,6 @@
                             if (isNewTypeShortFgs) {
                                 // Only in this case, we extend the SHORT_SERVICE time out.
                                 extendShortServiceTimeout = true;
-                                if (DEBUG_SHORT_SERVICE) {
-                                    Slog.i(TAG_SERVICE, "Extending SHORT_SERVICE time out: " + r);
-                                }
                             } else {
                                 // FGS type is changing from SHORT_SERVICE to another type when
                                 // an app is allowed to start FGS, so this will succeed.
@@ -2069,8 +2104,20 @@
                                 // maybeUpdateShortFgsTrackingLocked().
                             }
                         } else {
-                            // We catch this case later, in the
-                            // "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
+                            if (isNewTypeShortFgs) {
+                                // startForeground(SHORT_SERVICE) is called on an already running
+                                // SHORT_SERVICE FGS, when BFSL is not allowed.
+                                // In this case, the call should succeed
+                                // (== ForegroundServiceStartNotAllowedException shouldn't be
+                                // thrown), but the short service timeout shouldn't extend
+                                // (== extendShortServiceTimeout should be false).
+                                // We still do everything else -- e.g. we still need to update
+                                // the notification.
+                                bypassBfslCheck = true;
+                            } else {
+                                // We catch this case later, in the
+                                // "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
+                            }
                         }
 
                     } else if (r.mStartForegroundCount == 0) {
@@ -2125,23 +2172,25 @@
                                         + "location/camera/microphone access: service "
                                         + r.shortInstanceName);
                     }
-                    logFgsBackgroundStart(r);
-                    if (r.mAllowStartForeground == REASON_DENIED
-                            && isBgFgsRestrictionEnabledForService) {
-                        final String msg = "Service.startForeground() not allowed due to "
-                                + "mAllowStartForeground false: service "
-                                + r.shortInstanceName
-                                + (isOldTypeShortFgs ? " (Called on SHORT_SERVICE)" : "");
-                        Slog.w(TAG, msg);
-                        showFgsBgRestrictedNotificationLocked(r);
-                        updateServiceForegroundLocked(psr, true);
-                        ignoreForeground = true;
-                        logFGSStateChangeLocked(r,
-                                FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
-                                0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
-                        if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
-                                r.appInfo.uid)) {
-                            throw new ForegroundServiceStartNotAllowedException(msg);
+                    if (!bypassBfslCheck) {
+                        logFgsBackgroundStart(r);
+                        if (r.mAllowStartForeground == REASON_DENIED
+                                && isBgFgsRestrictionEnabledForService) {
+                            final String msg = "Service.startForeground() not allowed due to "
+                                    + "mAllowStartForeground false: service "
+                                    + r.shortInstanceName
+                                    + (isOldTypeShortFgs ? " (Called on SHORT_SERVICE)" : "");
+                            Slog.w(TAG, msg);
+                            showFgsBgRestrictedNotificationLocked(r);
+                            updateServiceForegroundLocked(psr, true);
+                            ignoreForeground = true;
+                            logFGSStateChangeLocked(r,
+                                    FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+                                    0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
+                            if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
+                                    r.appInfo.uid)) {
+                                throw new ForegroundServiceStartNotAllowedException(msg);
+                            }
                         }
                     }
 
@@ -3084,11 +3133,17 @@
             unscheduleShortFgsTimeoutLocked(sr);
             return;
         }
-        if (DEBUG_SHORT_SERVICE) {
-            Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
-        }
 
-        if (extendTimeout || !sr.hasShortFgsInfo()) {
+        final boolean isAlreadyShortFgs = sr.hasShortFgsInfo();
+
+        if (extendTimeout || !isAlreadyShortFgs) {
+            if (DEBUG_SHORT_SERVICE) {
+                if (isAlreadyShortFgs) {
+                    Slog.i(TAG_SERVICE, "Extending SHORT_SERVICE time out: " + sr);
+                } else {
+                    Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
+                }
+            }
             sr.setShortFgsInfo(SystemClock.uptimeMillis());
 
             // We'll restart the timeout.
@@ -3098,6 +3153,10 @@
                     ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
             mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getTimeoutTime());
         } else {
+            if (DEBUG_SHORT_SERVICE) {
+                Slog.w(TAG_SERVICE, "NOT extending SHORT_SERVICE time out: " + sr);
+            }
+
             // We only (potentially) update the start command, start count, but not the timeout
             // time.
             // In this case, we keep the existing timeout running.
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 44f475f..9e95e5f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -129,6 +129,7 @@
     static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE = "kill_bg_restricted_cached_idle";
     static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME =
             "kill_bg_restricted_cached_idle_settle_time";
+    static final String KEY_MAX_PREVIOUS_TIME = "max_previous_time";
     /**
      * Note this key is on {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS}.
      * @see #mEnableComponentAlias
@@ -145,6 +146,9 @@
      */
     static final String KEY_NETWORK_ACCESS_TIMEOUT_MS = "network_access_timeout_ms";
 
+    static final String KEY_USE_TIERED_CACHED_ADJ = "use_tiered_cached_adj";
+    static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time";
+
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
     private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
@@ -192,6 +196,7 @@
     private static final float DEFAULT_FGS_START_DENIED_LOG_SAMPLE_RATE = 1; // 100%
     private static final long DEFAULT_PROCESS_KILL_TIMEOUT_MS = 10 * 1000;
     private static final long DEFAULT_NETWORK_ACCESS_TIMEOUT_MS = 200; // 0.2 sec
+    private static final long DEFAULT_MAX_PREVIOUS_TIME = 60 * 1000; // 60s
 
     static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60 * 1000;
     static final long DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME_MS = 60 * 1000;
@@ -199,6 +204,9 @@
 
     static final int DEFAULT_MAX_SERVICE_CONNECTIONS_PER_PROCESS = 3000;
 
+    private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false;
+    private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000;
+
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
      */
@@ -534,6 +542,9 @@
     public long TOP_TO_ALMOST_PERCEPTIBLE_GRACE_DURATION =
             DEFAULT_TOP_TO_ALMOST_PERCEPTIBLE_GRACE_DURATION;
 
+    // How long a process can remain at previous oom_adj before dropping to cached
+    public static long MAX_PREVIOUS_TIME = DEFAULT_MAX_PREVIOUS_TIME;
+
     /**
      * The minimum time we allow between crashes, for us to consider this
      * application to be bad and stop its services and reject broadcasts.
@@ -1006,6 +1017,12 @@
     public volatile long mShortFgsAnrExtraWaitDuration =
             DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;
 
+    /** @see #KEY_USE_TIERED_CACHED_ADJ */
+    public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ;
+
+    /** @see #KEY_TIERED_CACHED_ADJ_DECAY_TIME */
+    public long TIERED_CACHED_ADJ_DECAY_TIME = DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME;
+
     private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
             new OnPropertiesChangedListener() {
                 @Override
@@ -1171,6 +1188,13 @@
                             case KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION:
                                 updateEnableWaitForFinishAttachApplication();
                                 break;
+                            case KEY_MAX_PREVIOUS_TIME:
+                                updateMaxPreviousTime();
+                                break;
+                            case KEY_USE_TIERED_CACHED_ADJ:
+                            case KEY_TIERED_CACHED_ADJ_DECAY_TIME:
+                                updateUseTieredCachedAdj();
+                                break;
                             default:
                                 break;
                         }
@@ -1825,6 +1849,7 @@
                 DEFAULT_LOW_SWAP_THRESHOLD_PERCENT);
     }
 
+
     private void updateTopToFgsGraceDuration() {
         TOP_TO_FGS_GRACE_DURATION = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1832,6 +1857,13 @@
                 DEFAULT_TOP_TO_FGS_GRACE_DURATION);
     }
 
+    private void updateMaxPreviousTime() {
+        MAX_PREVIOUS_TIME = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_MAX_PREVIOUS_TIME,
+                DEFAULT_MAX_PREVIOUS_TIME);
+    }
+
     private void updateMinAssocLogDuration() {
         MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION,
@@ -1908,6 +1940,17 @@
                 DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION);
     }
 
+    private void updateUseTieredCachedAdj() {
+        USE_TIERED_CACHED_ADJ = DeviceConfig.getBoolean(
+            DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+            KEY_USE_TIERED_CACHED_ADJ,
+            DEFAULT_USE_TIERED_CACHED_ADJ);
+        TIERED_CACHED_ADJ_DECAY_TIME = DeviceConfig.getLong(
+            DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+            KEY_TIERED_CACHED_ADJ_DECAY_TIME,
+            DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME);
+    }
+
     @NeverCompile // Avoid size overhead of debugging code.
     void dump(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
@@ -2092,6 +2135,11 @@
         pw.print("  "); pw.print(KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
         pw.print("="); pw.println(mShortFgsAnrExtraWaitDuration);
 
+        pw.print("  "); pw.print(KEY_USE_TIERED_CACHED_ADJ);
+        pw.print("="); pw.println(USE_TIERED_CACHED_ADJ);
+        pw.print("  "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME);
+        pw.print("="); pw.println(TIERED_CACHED_ADJ_DECAY_TIME);
+
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
             pw.print("  mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 3f06990..928af3f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -17,8 +17,10 @@
 package com.android.server.am;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Context.BindServiceFlags;
 import android.content.Context.BindServiceFlagsBits;
@@ -68,9 +70,53 @@
     void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
 
     /**
-     * Binds to a sdk sandbox service, creating it if needed. You can through the arguments
-     * here have the system bring up multiple concurrent processes hosting their own instance of
-     * that service. The {@code processName} you provide here identifies the different instances.
+     * Requests that an SDK sandbox service be started. If this service is not already running,
+     * it will be instantiated and started (creating a process for it if needed). You can through
+     * the arguments here have the system bring up multiple concurrent processes hosting their own
+     * instance of that service. Each instance is identified by the {@code processName} provided
+     * here.
+     *
+     * @param service Identifies the sdk sandbox process service to connect to. The Intent must
+     *                specify an explicit component name. This value cannot be null.
+     * @param clientAppUid Uid of the app for which the sdk sandbox process needs to be spawned.
+     * @param clientAppPackage Package of the app for which the sdk sandbox process needs to
+     *        be spawned. This package must belong to the clientAppUid.
+     * @param processName Unique identifier for the service instance. Each unique name here will
+     *        result in a different service instance being created. Identifiers must only contain
+     *        ASCII letters, digits, underscores, and periods.
+     *
+     * @throws RemoteException If the service could not be started.
+     * @return If the service is being started or is already running, the {@link ComponentName} of
+     * the actual service that was started is returned; else if the service does not exist null is
+     * returned.
+     */
+    @Nullable
+    @SuppressLint("RethrowRemoteException")
+    ComponentName startSdkSandboxService(@NonNull Intent service, int clientAppUid,
+            @NonNull String clientAppPackage, @NonNull String processName)
+            throws RemoteException;
+
+    // TODO(b/269592470): What if the sandbox is stopped while there is an active binding to it?
+    /**
+     * Requests that an SDK sandbox service with a given {@code processName} be stopped.
+     *
+     * @param service Identifies the sdk sandbox process service to connect to. The Intent must
+     *        specify an explicit component name. This value cannot be null.
+     * @param clientAppUid Uid of the app for which the sdk sandbox process needs to be stopped.
+     * @param clientAppPackage Package of the app for which the sdk sandbox process needs to
+     *        be stopped. This package must belong to the clientAppUid.
+     * @param processName Unique identifier for the service instance. Each unique name here will
+     *        result in a different service instance being created. Identifiers must only contain
+     *        ASCII letters, digits, underscores, and periods.
+     *
+     * @return If there is a service matching the given Intent that is already running, then it is
+     *         stopped and true is returned; else false is returned.
+     */
+    boolean stopSdkSandboxService(@NonNull Intent service, int clientAppUid,
+            @NonNull String clientAppPackage, @NonNull String processName);
+
+    /**
+     * Binds to an SDK sandbox service for a given client application.
      *
      * @param service Identifies the sdk sandbox process service to connect to. The Intent must
      *        specify an explicit component name. This value cannot be null.
@@ -90,7 +136,7 @@
      *         service that your client has permission to bind to; {@code false}
      *         if the system couldn't find the service or if your client doesn't
      *         have permission to bind to it.
-     * @throws RemoteException If the service could not be brought up.
+     * @throws RemoteException If the service could not be bound to.
      * @see Context#bindService(Intent, ServiceConnection, int)
      */
     @SuppressLint("RethrowRemoteException")
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5ad0409..74f7990 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -215,6 +215,7 @@
 import android.app.PendingIntentStats;
 import android.app.ProcessMemoryState;
 import android.app.ProfilerInfo;
+import android.app.ServiceStartNotAllowedException;
 import android.app.SyncNotedAppOp;
 import android.app.WaitResult;
 import android.app.assist.ActivityId;
@@ -1127,6 +1128,19 @@
         }
 
         @Override
+        protected void filterResults(@NonNull Computer computer,
+                @NonNull Intent intent, List<BroadcastFilter> results) {
+            if (intent.getAction() != null) return;
+            // When the resolved component is targeting U+, block null action intents
+            for (int i = results.size() - 1; i >= 0; --i) {
+                if (computer.isChangeEnabled(
+                        IntentFilter.BLOCK_NULL_ACTION_INTENTS, results.get(i).owningUid)) {
+                    results.remove(i);
+                }
+            }
+        }
+
+        @Override
         protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
             return input;
         }
@@ -13156,6 +13170,15 @@
             String resolvedType, boolean requireForeground, String callingPackage,
             String callingFeatureId, int userId)
             throws TransactionTooLargeException {
+        return startService(caller, service, resolvedType, requireForeground, callingPackage,
+                callingFeatureId, userId, false /* isSdkSandboxService */, INVALID_UID, null, null);
+    }
+
+    private ComponentName startService(IApplicationThread caller, Intent service,
+            String resolvedType, boolean requireForeground, String callingPackage,
+            String callingFeatureId, int userId, boolean isSdkSandboxService,
+            int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String instanceName)
+            throws TransactionTooLargeException {
         enforceNotIsolatedCaller("startService");
         enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
         // Refuse possible leaked file descriptors
@@ -13167,6 +13190,11 @@
             throw new IllegalArgumentException("callingPackage cannot be null");
         }
 
+        if (isSdkSandboxService && instanceName == null) {
+            throw new IllegalArgumentException("No instance name provided for SDK sandbox process");
+        }
+        validateServiceInstanceName(instanceName);
+
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
                 "*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
         final int callingPid = Binder.getCallingPid();
@@ -13182,7 +13210,9 @@
             synchronized (this) {
                 res = mServices.startServiceLocked(caller, service,
                         resolvedType, callingPid, callingUid,
-                        requireForeground, callingPackage, callingFeatureId, userId);
+                        requireForeground, callingPackage, callingFeatureId, userId,
+                        isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
+                        instanceName);
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -13191,9 +13221,26 @@
         return res;
     }
 
+    private void validateServiceInstanceName(String instanceName) {
+        // Ensure that instanceName, which is caller provided, does not contain
+        // unusual characters.
+        if (instanceName != null) {
+            if (!instanceName.matches("[a-zA-Z0-9_.]+")) {
+                throw new IllegalArgumentException("Illegal instanceName");
+            }
+        }
+    }
+
     @Override
     public int stopService(IApplicationThread caller, Intent service,
             String resolvedType, int userId) {
+        return stopService(caller, service, resolvedType, userId, false /* isSdkSandboxService */,
+                INVALID_UID, null, null);
+    }
+
+    private int stopService(IApplicationThread caller, Intent service, String resolvedType,
+            int userId, boolean isSdkSandboxService,
+            int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String instanceName) {
         enforceNotIsolatedCaller("stopService");
         // Refuse possible leaked file descriptors
         if (service != null && service.hasFileDescriptors() == true) {
@@ -13205,7 +13252,9 @@
                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "stopService: " + service);
             }
             synchronized (this) {
-                return mServices.stopServiceLocked(caller, service, resolvedType, userId);
+                return mServices.stopServiceLocked(caller, service, resolvedType, userId,
+                        isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
+                        instanceName);
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -13364,17 +13413,7 @@
             throw new IllegalArgumentException("No instance name provided for isolated process");
         }
 
-        // Ensure that instanceName, which is caller provided, does not contain
-        // unusual characters.
-        if (instanceName != null) {
-            for (int i = 0; i < instanceName.length(); ++i) {
-                char c = instanceName.charAt(i);
-                if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
-                            || (c >= '0' && c <= '9') || c == '_' || c == '.')) {
-                    throw new IllegalArgumentException("Illegal instanceName");
-                }
-            }
-        }
+        validateServiceInstanceName(instanceName);
 
         try {
             if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -13939,11 +13978,19 @@
                         (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {
                     continue;
                 }
+
+                final boolean blockNullAction = mPlatformCompat.isChangeEnabledInternal(
+                        IntentFilter.BLOCK_NULL_ACTION_INTENTS, callerApp.info);
                 // If intent has scheme "content", it will need to access
                 // provider that needs to lock mProviderMap in ActivityThread
                 // and also it may need to wait application response, so we
                 // cannot lock ActivityManagerService here.
-                if (filter.match(resolver, intent, true, TAG) >= 0) {
+                if (filter.match(intent.getAction(), intent.resolveType(resolver),
+                        intent.getScheme(), intent.getData(), intent.getCategories(), TAG,
+                        false /* supportWildcards */,
+                        blockNullAction,
+                        null /* ignoreActions */,
+                        intent.getExtras()) >= 0) {
                     if (allSticky == null) {
                         allSticky = new ArrayList<Intent>();
                     }
@@ -14962,7 +15009,7 @@
                     }
                     List<BroadcastFilter> registeredReceiversForUser =
                             mReceiverResolver.queryIntent(snapshot, intent,
-                                    resolvedType, false /*defaultOnly*/, users[i]);
+                                    resolvedType, false /*defaultOnly*/, callingUid, users[i]);
                     if (registeredReceivers == null) {
                         registeredReceivers = registeredReceiversForUser;
                     } else if (registeredReceiversForUser != null) {
@@ -14971,7 +15018,7 @@
                 }
             } else {
                 registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
-                        resolvedType, false /*defaultOnly*/, userId);
+                        resolvedType, false /*defaultOnly*/, callingUid, userId);
             }
         }
         BroadcastQueue.traceEnd(cookie);
@@ -17271,6 +17318,53 @@
         }
 
         @Override
+        public ComponentName startSdkSandboxService(Intent service, int clientAppUid,
+                String clientAppPackage, String processName) throws RemoteException {
+            validateSdkSandboxParams(service, clientAppUid, clientAppPackage, processName);
+            // TODO(b/269598719): Is passing the application thread of the system_server alright?
+            // e.g. the sandbox getting privileged access due to this.
+            ComponentName cn = ActivityManagerService.this.startService(
+                    mContext.getIApplicationThread(), service,
+                    service.resolveTypeIfNeeded(mContext.getContentResolver()), false,
+                    mContext.getOpPackageName(), mContext.getAttributionTag(),
+                    UserHandle.getUserId(clientAppUid), true, clientAppUid, clientAppPackage,
+                    processName);
+            if (cn != null) {
+                if (cn.getPackageName().equals("!")) {
+                    throw new SecurityException(
+                            "Not allowed to start service " + service
+                                    + " without permission " + cn.getClassName());
+                } else if (cn.getPackageName().equals("!!")) {
+                    throw new SecurityException(
+                            "Unable to start service " + service
+                                    + ": " + cn.getClassName());
+                } else if (cn.getPackageName().equals("?")) {
+                    throw ServiceStartNotAllowedException.newInstance(false,
+                            "Not allowed to start service " + service + ": "
+                                    + cn.getClassName());
+                }
+            }
+
+            return cn;
+        }
+
+        @Override
+        public boolean stopSdkSandboxService(Intent service, int clientAppUid,
+                String clientAppPackage, String processName) {
+            validateSdkSandboxParams(service, clientAppUid, clientAppPackage, processName);
+            int res = ActivityManagerService.this.stopService(
+                    mContext.getIApplicationThread(), service,
+                    service.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    UserHandle.getUserId(clientAppUid), true, clientAppUid, clientAppPackage,
+                    processName);
+            if (res < 0) {
+                throw new SecurityException(
+                        "Not allowed to stop service " + service);
+            }
+            return res != 0;
+        }
+
+        @Override
         public boolean bindSdkSandboxService(Intent service, ServiceConnection conn,
                 int clientAppUid, IBinder clientApplicationThread, String clientAppPackage,
                 String processName, int flags)
@@ -17292,27 +17386,10 @@
                 int clientAppUid, IBinder clientApplicationThread, String clientAppPackage,
                 String processName, long flags)
                 throws RemoteException {
-            if (service == null) {
-                throw new IllegalArgumentException("intent is null");
-            }
+            validateSdkSandboxParams(service, clientAppUid, clientAppPackage, processName);
             if (conn == null) {
                 throw new IllegalArgumentException("connection is null");
             }
-            if (clientAppPackage == null) {
-                throw new IllegalArgumentException("clientAppPackage is null");
-            }
-            if (processName == null) {
-                throw new IllegalArgumentException("processName is null");
-            }
-            if (service.getComponent() == null) {
-                throw new IllegalArgumentException("service must specify explicit component");
-            }
-            if (!UserHandle.isApp(clientAppUid)) {
-                throw new IllegalArgumentException("uid is not within application range");
-            }
-            if (mAppOpsService.checkPackage(clientAppUid, clientAppPackage) != MODE_ALLOWED) {
-                throw new IllegalArgumentException("uid does not belong to provided package");
-            }
 
             Handler handler = mContext.getMainThreadHandler();
             IApplicationThread clientApplicationThreadVerified = null;
@@ -17345,6 +17422,28 @@
                     UserHandle.getUserId(clientAppUid)) != 0;
         }
 
+        private void validateSdkSandboxParams(Intent service, int clientAppUid,
+                String clientAppPackage, String processName) {
+            if (service == null) {
+                throw new IllegalArgumentException("intent is null");
+            }
+            if (clientAppPackage == null) {
+                throw new IllegalArgumentException("clientAppPackage is null");
+            }
+            if (processName == null) {
+                throw new IllegalArgumentException("processName is null");
+            }
+            if (service.getComponent() == null) {
+                throw new IllegalArgumentException("service must specify explicit component");
+            }
+            if (!UserHandle.isApp(clientAppUid)) {
+                throw new IllegalArgumentException("uid is not within application range");
+            }
+            if (mAppOpsService.checkPackage(clientAppUid, clientAppPackage) != MODE_ALLOWED) {
+                throw new IllegalArgumentException("uid does not belong to provided package");
+            }
+        }
+
         @Override
         public boolean bindSdkSandboxService(Intent service, ServiceConnection conn,
                 int clientAppUid, String clientAppPackage, String processName, int flags)
diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java
index 9be553c..01466b8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerUtils.java
+++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java
@@ -17,11 +17,13 @@
 
 import android.app.ActivityThread;
 import android.content.ContentResolver;
+import android.content.Intent;
 import android.provider.Settings;
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
@@ -133,4 +135,25 @@
     public static int hashComponentNameForAtom(String shortInstanceName) {
         return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash();
     }
+
+    /**
+     * Helper method to log an unsafe intent event.
+     */
+    public static void logUnsafeIntentEvent(int event, int callingUid,
+            Intent intent, String resolvedType, boolean blocked) {
+        String[] categories = intent.getCategories() == null ? new String[0]
+                : intent.getCategories().toArray(String[]::new);
+        String component = intent.getComponent() == null ? null
+                : intent.getComponent().flattenToString();
+        FrameworkStatsLog.write(FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED,
+                event,
+                callingUid,
+                component,
+                intent.getPackage(),
+                intent.getAction(),
+                categories,
+                resolvedType,
+                intent.getScheme(),
+                blocked);
+    }
 }
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 6619025..c2326f6 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -1388,7 +1388,7 @@
         final int mDefaultBgCurrentDrainPowerComponent;
 
         /**
-         * Default value to {@link #mBgCurrentDrainExmptedTypes}.
+         * Default value to {@link #mBgCurrentDrainExemptedTypes}.
          **/
         final int mDefaultBgCurrentDrainExemptedTypes;
 
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 913f151..f4685f0 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1921,9 +1921,26 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case SET_FROZEN_PROCESS_MSG:
+                {
+                    ProcessRecord proc = (ProcessRecord) msg.obj;
+                    int pid = proc.getPid();
+                    final String name = proc.processName;
                     synchronized (mAm) {
-                        freezeProcess((ProcessRecord) msg.obj);
+                        freezeProcess(proc);
                     }
+                    try {
+                        // post-check to prevent deadlock
+                        mProcLocksReader.handleBlockingFileLocks(this);
+                    } catch (Exception e) {
+                        Slog.e(TAG_AM, "Unable to check file locks for "
+                                + name + "(" + pid + "): " + e);
+                        synchronized (mAm) {
+                            synchronized (mProcLock) {
+                                unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+                            }
+                        }
+                    }
+                }
                     break;
                 case REPORT_UNFREEZE_MSG:
                     int pid = msg.arg1;
@@ -2057,16 +2074,6 @@
                     }
                 });
             }
-
-            try {
-                // post-check to prevent deadlock
-                mProcLocksReader.handleBlockingFileLocks(this);
-            } catch (Exception e) {
-                Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
-                synchronized (mProcLock) {
-                    unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
-                }
-            }
         }
 
         private void reportUnfreeze(int pid, int frozenDuration, String processName,
@@ -2123,22 +2130,25 @@
             if (DEBUG_FREEZER) {
                 Slog.d(TAG_AM, "Blocking file lock found: " + pids);
             }
-            synchronized (mProcLock) {
-                int pid = pids.get(0);
-                ProcessRecord app = mFrozenProcesses.get(pid);
-                ProcessRecord pr;
-                if (app != null) {
-                    for (int i = 1; i < pids.size(); i++) {
-                        int blocked = pids.get(i);
-                        synchronized (mAm.mPidsSelfLocked) {
-                            pr = mAm.mPidsSelfLocked.get(blocked);
-                        }
-                        if (pr != null && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
-                            Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
-                                    + pr.processName + " (" + blocked + ")");
-                            // Found at least one blocked non-cached process
-                            unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
-                            break;
+            synchronized (mAm) {
+                synchronized (mProcLock) {
+                    int pid = pids.get(0);
+                    ProcessRecord app = mFrozenProcesses.get(pid);
+                    ProcessRecord pr;
+                    if (app != null) {
+                        for (int i = 1; i < pids.size(); i++) {
+                            int blocked = pids.get(i);
+                            synchronized (mAm.mPidsSelfLocked) {
+                                pr = mAm.mPidsSelfLocked.get(blocked);
+                            }
+                            if (pr != null
+                                    && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
+                                Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+                                        + pr.processName + " (" + blocked + ")");
+                                // Found at least one blocked non-cached process
+                                unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+                                break;
+                            }
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index c0cb7d9..993595b 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -316,6 +316,10 @@
         // then we should care, otherwise we assume
         // it's not related to any FGS
         UidState uidState = mUids.get(uid);
+        if (uidState == null) {
+            Log.w(TAG, "API event end called before start!");
+            return -1;
+        }
         if (uidState.mOpenWithFgsCount.contains(apiType)) {
             // are there any calls that started with an FGS?
             if (uidState.mOpenWithFgsCount.get(apiType) != 0) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 32db33d..0c36626 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1073,135 +1073,165 @@
     @GuardedBy({"mService", "mProcLock"})
     private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
         final int numLru = lruList.size();
+        if (mConstants.USE_TIERED_CACHED_ADJ) {
+            final long now = SystemClock.uptimeMillis();
+            for (int i = numLru - 1; i >= 0; i--) {
+                ProcessRecord app = lruList.get(i);
+                final ProcessStateRecord state = app.mState;
+                final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+                if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
+                        >= UNKNOWN_ADJ) {
+                    final ProcessServiceRecord psr = app.mServices;
+                    int targetAdj = CACHED_APP_MIN_ADJ;
 
-        // First update the OOM adjustment for each of the
-        // application processes based on their current state.
-        int curCachedAdj = CACHED_APP_MIN_ADJ;
-        int nextCachedAdj = curCachedAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
-        int curCachedImpAdj = 0;
-        int curEmptyAdj = CACHED_APP_MIN_ADJ + CACHED_APP_IMPORTANCE_LEVELS;
-        int nextEmptyAdj = curEmptyAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
+                    if (opt != null && opt.isFreezeExempt()) {
+                        // BIND_WAIVE_PRIORITY and the like get oom_adj 900
+                        targetAdj += 0;
+                    } else if ((state.getSetAdj() >= CACHED_APP_MIN_ADJ)
+                            && (state.getLastStateTime()
+                                    + mConstants.TIERED_CACHED_ADJ_DECAY_TIME) < now) {
+                        // Older cached apps get 950
+                        targetAdj += 50;
+                    } else {
+                        // Newer cached apps get 910
+                        targetAdj += 10;
+                    }
+                    state.setCurRawAdj(targetAdj);
+                    state.setCurAdj(psr.modifyRawOomAdj(targetAdj));
+                }
+            }
+        } else {
+            // First update the OOM adjustment for each of the
+            // application processes based on their current state.
+            int curCachedAdj = CACHED_APP_MIN_ADJ;
+            int nextCachedAdj = curCachedAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
+            int curCachedImpAdj = 0;
+            int curEmptyAdj = CACHED_APP_MIN_ADJ + CACHED_APP_IMPORTANCE_LEVELS;
+            int nextEmptyAdj = curEmptyAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
 
-        final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
-        final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES
-                - emptyProcessLimit;
-        // Let's determine how many processes we have running vs.
-        // how many slots we have for background processes; we may want
-        // to put multiple processes in a slot of there are enough of
-        // them.
-        int numEmptyProcs = numLru - mNumNonCachedProcs - mNumCachedHiddenProcs;
-        if (numEmptyProcs > cachedProcessLimit) {
-            // If there are more empty processes than our limit on cached
-            // processes, then use the cached process limit for the factor.
-            // This ensures that the really old empty processes get pushed
-            // down to the bottom, so if we are running low on memory we will
-            // have a better chance at keeping around more cached processes
-            // instead of a gazillion empty processes.
-            numEmptyProcs = cachedProcessLimit;
-        }
-        int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + mNumSlots - 1) : 1)
-                / mNumSlots;
-        if (cachedFactor < 1) cachedFactor = 1;
+            final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
+            final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES
+                                           - emptyProcessLimit;
+            // Let's determine how many processes we have running vs.
+            // how many slots we have for background processes; we may want
+            // to put multiple processes in a slot of there are enough of
+            // them.
+            int numEmptyProcs = numLru - mNumNonCachedProcs - mNumCachedHiddenProcs;
+            if (numEmptyProcs > cachedProcessLimit) {
+                // If there are more empty processes than our limit on cached
+                // processes, then use the cached process limit for the factor.
+                // This ensures that the really old empty processes get pushed
+                // down to the bottom, so if we are running low on memory we will
+                // have a better chance at keeping around more cached processes
+                // instead of a gazillion empty processes.
+                numEmptyProcs = cachedProcessLimit;
+            }
+            int cachedFactor = (mNumCachedHiddenProcs > 0
+                    ? (mNumCachedHiddenProcs + mNumSlots - 1) : 1)
+                               / mNumSlots;
+            if (cachedFactor < 1) cachedFactor = 1;
 
-        int emptyFactor = (numEmptyProcs + mNumSlots - 1) / mNumSlots;
-        if (emptyFactor < 1) emptyFactor = 1;
+            int emptyFactor = (numEmptyProcs + mNumSlots - 1) / mNumSlots;
+            if (emptyFactor < 1) emptyFactor = 1;
 
-        int stepCached = -1;
-        int stepEmpty = -1;
-        int lastCachedGroup = 0;
-        int lastCachedGroupImportance = 0;
-        int lastCachedGroupUid = 0;
+            int stepCached = -1;
+            int stepEmpty = -1;
+            int lastCachedGroup = 0;
+            int lastCachedGroupImportance = 0;
+            int lastCachedGroupUid = 0;
 
-        for (int i = numLru - 1; i >= 0; i--) {
-            ProcessRecord app = lruList.get(i);
-            final ProcessStateRecord state = app.mState;
-            // If we haven't yet assigned the final cached adj
-            // to the process, do that now.
-            if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
+
+            for (int i = numLru - 1; i >= 0; i--) {
+                ProcessRecord app = lruList.get(i);
+                final ProcessStateRecord state = app.mState;
+                // If we haven't yet assigned the final cached adj
+                // to the process, do that now.
+                if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
                     >= UNKNOWN_ADJ) {
-                final ProcessServiceRecord psr = app.mServices;
-                switch (state.getCurProcState()) {
-                    case PROCESS_STATE_CACHED_ACTIVITY:
-                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
-                    case ActivityManager.PROCESS_STATE_CACHED_RECENT:
-                        // Figure out the next cached level, taking into account groups.
-                        boolean inGroup = false;
-                        final int connectionGroup = psr.getConnectionGroup();
-                        if (connectionGroup != 0) {
-                            final int connectionImportance = psr.getConnectionImportance();
-                            if (lastCachedGroupUid == app.uid
+                    final ProcessServiceRecord psr = app.mServices;
+                    switch (state.getCurProcState()) {
+                        case PROCESS_STATE_CACHED_ACTIVITY:
+                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+                        case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+                            // Figure out the next cached level, taking into account groups.
+                            boolean inGroup = false;
+                            final int connectionGroup = psr.getConnectionGroup();
+                            if (connectionGroup != 0) {
+                                final int connectionImportance = psr.getConnectionImportance();
+                                if (lastCachedGroupUid == app.uid
                                     && lastCachedGroup == connectionGroup) {
-                                // This is in the same group as the last process, just tweak
-                                // adjustment by importance.
-                                if (connectionImportance > lastCachedGroupImportance) {
-                                    lastCachedGroupImportance = connectionImportance;
-                                    if (curCachedAdj < nextCachedAdj
+                                    // This is in the same group as the last process, just tweak
+                                    // adjustment by importance.
+                                    if (connectionImportance > lastCachedGroupImportance) {
+                                        lastCachedGroupImportance = connectionImportance;
+                                        if (curCachedAdj < nextCachedAdj
                                             && curCachedAdj < CACHED_APP_MAX_ADJ) {
-                                        curCachedImpAdj++;
+                                            curCachedImpAdj++;
+                                        }
+                                    }
+                                    inGroup = true;
+                                } else {
+                                    lastCachedGroupUid = app.uid;
+                                    lastCachedGroup = connectionGroup;
+                                    lastCachedGroupImportance = connectionImportance;
+                                }
+                            }
+                            if (!inGroup && curCachedAdj != nextCachedAdj) {
+                                stepCached++;
+                                curCachedImpAdj = 0;
+                                if (stepCached >= cachedFactor) {
+                                    stepCached = 0;
+                                    curCachedAdj = nextCachedAdj;
+                                    nextCachedAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
+                                    if (nextCachedAdj > CACHED_APP_MAX_ADJ) {
+                                        nextCachedAdj = CACHED_APP_MAX_ADJ;
                                     }
                                 }
-                                inGroup = true;
-                            } else {
-                                lastCachedGroupUid = app.uid;
-                                lastCachedGroup = connectionGroup;
-                                lastCachedGroupImportance = connectionImportance;
                             }
-                        }
-                        if (!inGroup && curCachedAdj != nextCachedAdj) {
-                            stepCached++;
-                            curCachedImpAdj = 0;
-                            if (stepCached >= cachedFactor) {
-                                stepCached = 0;
-                                curCachedAdj = nextCachedAdj;
-                                nextCachedAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
-                                if (nextCachedAdj > CACHED_APP_MAX_ADJ) {
-                                    nextCachedAdj = CACHED_APP_MAX_ADJ;
+                            // This process is a cached process holding activities...
+                            // assign it the next cached value for that type, and then
+                            // step that cached level.
+                            state.setCurRawAdj(curCachedAdj + curCachedImpAdj);
+                            state.setCurAdj(psr.modifyRawOomAdj(curCachedAdj + curCachedImpAdj));
+                            if (DEBUG_LRU) {
+                                Slog.d(TAG_LRU, "Assigning activity LRU #" + i
+                                        + " adj: " + state.getCurAdj()
+                                        + " (curCachedAdj=" + curCachedAdj
+                                        + " curCachedImpAdj=" + curCachedImpAdj + ")");
+                            }
+                            break;
+                        default:
+                            // Figure out the next cached level.
+                            if (curEmptyAdj != nextEmptyAdj) {
+                                stepEmpty++;
+                                if (stepEmpty >= emptyFactor) {
+                                    stepEmpty = 0;
+                                    curEmptyAdj = nextEmptyAdj;
+                                    nextEmptyAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
+                                    if (nextEmptyAdj > CACHED_APP_MAX_ADJ) {
+                                        nextEmptyAdj = CACHED_APP_MAX_ADJ;
+                                    }
                                 }
                             }
-                        }
-                        // This process is a cached process holding activities...
-                        // assign it the next cached value for that type, and then
-                        // step that cached level.
-                        state.setCurRawAdj(curCachedAdj + curCachedImpAdj);
-                        state.setCurAdj(psr.modifyRawOomAdj(curCachedAdj + curCachedImpAdj));
-                        if (DEBUG_LRU) {
-                            Slog.d(TAG_LRU, "Assigning activity LRU #" + i
-                                    + " adj: " + state.getCurAdj()
-                                    + " (curCachedAdj=" + curCachedAdj
-                                    + " curCachedImpAdj=" + curCachedImpAdj + ")");
-                        }
-                        break;
-                    default:
-                        // Figure out the next cached level.
-                        if (curEmptyAdj != nextEmptyAdj) {
-                            stepEmpty++;
-                            if (stepEmpty >= emptyFactor) {
-                                stepEmpty = 0;
-                                curEmptyAdj = nextEmptyAdj;
-                                nextEmptyAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
-                                if (nextEmptyAdj > CACHED_APP_MAX_ADJ) {
-                                    nextEmptyAdj = CACHED_APP_MAX_ADJ;
-                                }
+                            // For everything else, assign next empty cached process
+                            // level and bump that up.  Note that this means that
+                            // long-running services that have dropped down to the
+                            // cached level will be treated as empty (since their process
+                            // state is still as a service), which is what we want.
+                            state.setCurRawAdj(curEmptyAdj);
+                            state.setCurAdj(psr.modifyRawOomAdj(curEmptyAdj));
+                            if (DEBUG_LRU) {
+                                Slog.d(TAG_LRU, "Assigning empty LRU #" + i
+                                        + " adj: " + state.getCurAdj()
+                                        + " (curEmptyAdj=" + curEmptyAdj
+                                        + ")");
                             }
-                        }
-                        // For everything else, assign next empty cached process
-                        // level and bump that up.  Note that this means that
-                        // long-running services that have dropped down to the
-                        // cached level will be treated as empty (since their process
-                        // state is still as a service), which is what we want.
-                        state.setCurRawAdj(curEmptyAdj);
-                        state.setCurAdj(psr.modifyRawOomAdj(curEmptyAdj));
-                        if (DEBUG_LRU) {
-                            Slog.d(TAG_LRU, "Assigning empty LRU #" + i
-                                    + " adj: " + state.getCurAdj() + " (curEmptyAdj=" + curEmptyAdj
-                                    + ")");
-                        }
-                        break;
+                            break;
+                    }
                 }
             }
         }
     }
-
     private long mNextNoKillDebugMessageTime;
 
     private double mLastFreeSwapPercent = 1.00;
@@ -1318,6 +1348,13 @@
                     // left sitting around after no longer needed.
                     app.killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
                             ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true);
+                } else if (app.isSdkSandbox && psr.numberOfRunningServices() <= 0
+                        && app.getActiveInstrumentation() == null) {
+                    // If this is an SDK sandbox process and there are no services running it, we
+                    // aggressively kill the sandbox as we usually don't want to re-use the same
+                    // sandbox again.
+                    app.killLocked("sandbox not needed", ApplicationExitInfo.REASON_OTHER,
+                            ApplicationExitInfo.SUBREASON_SDK_SANDBOX_NOT_NEEDED, true);
                 } else {
                     // Keeping this process, update its uid.
                     updateAppUidRecLSP(app);
@@ -2018,25 +2055,37 @@
                 }
             }
         }
-
         if (state.getCachedIsPreviousProcess() && state.getCachedHasActivities()) {
-            if (adj > PREVIOUS_APP_ADJ) {
-                // This was the previous process that showed UI to the user.
-                // We want to try to keep it around more aggressively, to give
-                // a good experience around switching between two apps.
-                adj = PREVIOUS_APP_ADJ;
-                schedGroup = SCHED_GROUP_BACKGROUND;
-                state.setCached(false);
-                state.setAdjType("previous");
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
-                }
-            }
-            if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+            // This was the previous process that showed UI to the user.  We want to
+            // try to keep it around more aggressively, to give a good experience
+            // around switching between two apps. However, we don't want to keep the
+            // process in this privileged state indefinitely. Eventually, allow the
+            // app to be demoted to cached.
+            if ((state.getSetProcState() == PROCESS_STATE_LAST_ACTIVITY
+                    && (state.getLastStateTime() + mConstants.MAX_PREVIOUS_TIME) < now)) {
                 procState = PROCESS_STATE_LAST_ACTIVITY;
-                state.setAdjType("previous");
+                schedGroup = SCHED_GROUP_BACKGROUND;
+                state.setAdjType("previous-expired");
+                adj = CACHED_APP_MIN_ADJ;
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Expire prev adj: " + app);
+                }
+            } else {
+                if (adj > PREVIOUS_APP_ADJ) {
+                    adj = PREVIOUS_APP_ADJ;
+                    schedGroup = SCHED_GROUP_BACKGROUND;
+                    state.setCached(false);
+                    state.setAdjType("previous");
+                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
+                    }
+                }
+                if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+                    procState = PROCESS_STATE_LAST_ACTIVITY;
+                    state.setAdjType("previous");
+                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 14ecf9f..daf227c 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -69,7 +69,8 @@
     @Overridable
     private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER = 244637991;
     private static final String ENABLE_DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER =
-            "enable_default_rescind_bal_privileges_from_pending_intent_sender";
+            "DefaultRescindBalPrivilegesFromPendingIntentSender__"
+                    + "enable_default_rescind_bal_privileges_from_pending_intent_sender";
 
     public static final int FLAG_ACTIVITY_SENDER = 1 << 0;
     public static final int FLAG_BROADCAST_SENDER = 1 << 1;
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index dce88da..005ad20 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -23,6 +23,7 @@
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.hardware.biometrics.AuthenticateOptions;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
@@ -44,8 +45,8 @@
 /**
  * A class to keep track of the authentication state for a given client.
  */
-public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
-        implements AuthenticationConsumer {
+public abstract class AuthenticationClient<T, O extends AuthenticateOptions>
+        extends AcquisitionClient<T> implements AuthenticationConsumer {
 
     // New, has not started yet
     public static final int STATE_NEW = 0;
@@ -89,14 +90,15 @@
 
     public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
-            int targetUserId, long operationId, boolean restricted, @NonNull String owner,
-            int cookie, boolean requireConfirmation, int sensorId,
+            long operationId, boolean restricted, @NonNull O options,
+            int cookie, boolean requireConfirmation,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener,
             @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
             boolean shouldVibrate, int sensorStrength) {
-        super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
-                shouldVibrate, biometricLogger, biometricContext);
+        super(context, lazyDaemon, token, listener, options.getUserId(),
+                options.getOpPackageName(), cookie, options.getSensorId(), shouldVibrate,
+                biometricLogger, biometricContext);
         mIsStrongBiometric = isStrongBiometric;
         mOperationId = operationId;
         mRequireConfirmation = requireConfirmation;
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
index 0f1fe68..25651fa28 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.util.proto.ProtoOutputStream;
 
@@ -39,7 +40,7 @@
     List<T> getSensorProperties();
 
     /** Properties for the given sensor id. */
-    @NonNull
+    @Nullable
     T getSensorProperties(int sensorId);
 
     boolean isHardwareDetected(int sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
index 2263e80..a4b0a0e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
@@ -92,7 +92,7 @@
         final int previousBiometricState = mBiometricState;
 
         if (client instanceof AuthenticationClient) {
-            final AuthenticationClient<?> authClient = (AuthenticationClient<?>) client;
+            final AuthenticationClient<?, ?> authClient = (AuthenticationClient<?, ?>) client;
             if (authClient.isKeyguard()) {
                 mBiometricState = STATE_KEYGUARD_AUTH;
             } else if (authClient.isBiometricPrompt()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 5182968..fb64bcc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -64,9 +64,10 @@
             long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
             String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
             throws RemoteException {
-        mFaceService.prepareForAuthentication(mSensorId, requireConfirmation, token, operationId,
+        mFaceService.prepareForAuthentication(requireConfirmation, token, operationId,
                 sensorReceiver, new FaceAuthenticateOptions.Builder()
                         .setUserId(userId)
+                        .setSensorId(mSensorId)
                         .setOpPackageName(opPackageName)
                         .build(),
                 requestId, cookie, allowBackgroundAuthentication);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 1ee9f53..6d7b2cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -246,7 +246,6 @@
 
             super.authenticate_enforcePermission();
 
-            final int userId = options.getUserId();
             final String opPackageName = options.getOpPackageName();
             final boolean restricted = false; // Face APIs are private
             final int statsClient = Utils.isKeyguard(getContext(), opPackageName)
@@ -261,9 +260,11 @@
             if (provider == null) {
                 Slog.w(TAG, "Null provider for authenticate");
                 return -1;
+            } else {
+                options.setSensorId(provider.first);
             }
 
-            return provider.second.scheduleAuthenticate(provider.first, token, operationId,
+            return provider.second.scheduleAuthenticate(token, operationId,
                     0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
                     restricted, statsClient, isKeyguard);
         }
@@ -286,28 +287,27 @@
                 return -1;
             }
 
-            return provider.second.scheduleFaceDetect(provider.first, token, options.getUserId(),
-                    new ClientMonitorCallbackConverter(receiver), opPackageName,
+            return provider.second.scheduleFaceDetect(token,
+                    new ClientMonitorCallbackConverter(receiver), options,
                     BiometricsProtoEnums.CLIENT_KEYGUARD);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
-        public void prepareForAuthentication(int sensorId, boolean requireConfirmation,
+        public void prepareForAuthentication(boolean requireConfirmation,
                 IBinder token, long operationId, IBiometricSensorReceiver sensorReceiver,
                 FaceAuthenticateOptions options, long requestId, int cookie,
                 boolean allowBackgroundAuthentication) {
             super.prepareForAuthentication_enforcePermission();
 
-            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(options.getSensorId());
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
                 return;
             }
 
-            final boolean isKeyguardBypassEnabled = false; // only valid for keyguard clients
             final boolean restricted = true; // BiometricPrompt is always restricted
-            provider.scheduleAuthenticate(sensorId, token, operationId, cookie,
+            provider.scheduleAuthenticate(token, operationId, cookie,
                     new ClientMonitorCallbackConverter(sensorReceiver), options, requestId,
                     restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
                     allowBackgroundAuthentication);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 609c6a7..2cf64b7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -83,18 +83,19 @@
 
     void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
 
-    long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
-            @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
+    long scheduleFaceDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FaceAuthenticateOptions options,
             int statsClient);
 
     void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId);
 
-    long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull FaceAuthenticateOptions options,
             boolean restricted, int statsClient, boolean allowBackgroundAuthentication);
 
-    void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    void scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull FaceAuthenticateOptions options, long requestId,
             boolean restricted, int statsClient, boolean allowBackgroundAuthentication);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 29dd707..976f1cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -28,6 +28,7 @@
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.face.IFace;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceAuthenticationFrame;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
@@ -56,7 +57,7 @@
 /**
  * Face-specific authentication client for the {@link IFace} AIDL HAL interface.
  */
-class FaceAuthenticationClient extends AuthenticationClient<AidlSession>
+class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>
         implements LockoutConsumer {
     private static final String TAG = "FaceAuthenticationClient";
 
@@ -80,16 +81,16 @@
     FaceAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
-            boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener, long operationId,
+            boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
+            boolean requireConfirmation,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
             @Authenticators.Types int sensorStrength) {
-        this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId,
-                restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
-                isStrongBiometric, usageStats, lockoutCache /* lockoutCache */,
-                allowBackgroundAuthentication,
+        this(context, lazyDaemon, token, requestId, listener, operationId,
+                restricted, options, cookie, requireConfirmation, logger, biometricContext,
+                isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
                 context.getSystemService(SensorPrivacyManager.class), sensorStrength);
     }
 
@@ -97,15 +98,16 @@
     FaceAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
-            boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener, long operationId,
+            boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
+            boolean requireConfirmation,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
             SensorPrivacyManager sensorPrivacyManager,
             @Authenticators.Types int biometricStrength) {
-        super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+        super(context, lazyDaemon, token, listener, operationId, restricted,
+                options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */,
                 allowBackgroundAuthentication, false /* shouldVibrate */,
                 biometricStrength);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 506b2bc..e65202d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -22,6 +22,7 @@
 import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -51,11 +52,11 @@
 
     FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FaceAuthenticateOptions options,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric) {
-        this(context, lazyDaemon, token, requestId, listener, userId, owner, sensorId,
+        this(context, lazyDaemon, token, requestId, listener, options,
                 logger, biometricContext, isStrongBiometric,
                 context.getSystemService(SensorPrivacyManager.class));
     }
@@ -63,11 +64,12 @@
     @VisibleForTesting
     FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FaceAuthenticateOptions options,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, SensorPrivacyManager sensorPrivacyManager) {
-        super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+        super(context, lazyDaemon, token, listener, options.getUserId(),
+                options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, logger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 41e0269..cf8ea86 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -410,16 +410,17 @@
     }
 
     @Override
-    public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
-            int userId, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, int statsClient) {
+    public long scheduleFaceDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FaceAuthenticateOptions options, int statsClient) {
         final long id = mRequestCounter.incrementAndGet();
+        final int sensorId = options.getSensorId();
 
         mHandler.post(() -> {
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FaceDetectClient client = new FaceDetectClient(mContext,
                     mSensors.get(sensorId).getLazySession(),
-                    token, id, callback, userId, opPackageName, sensorId,
+                    token, id, callback, options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric);
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -435,18 +436,19 @@
     }
 
     @Override
-    public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull FaceAuthenticateOptions options,
             long requestId, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         mHandler.post(() -> {
             final int userId = options.getUserId();
+            final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
                     mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
-                    userId, operationId, restricted, options.getOpPackageName(), cookie,
-                    false /* requireConfirmation */, sensorId,
+                    operationId, restricted, options, cookie,
+                    false /* requireConfirmation */,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mUsageStats, mSensors.get(sensorId).getLockoutCache(),
@@ -470,13 +472,13 @@
     }
 
     @Override
-    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         final long id = mRequestCounter.incrementAndGet();
 
-        scheduleAuthenticate(sensorId, token, operationId, cookie, callback,
+        scheduleAuthenticate(token, operationId, cookie, callback,
                 options, id, restricted, statsClient, allowBackgroundAuthentication);
 
         return id;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 7e575bc..0f22296 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -651,9 +651,9 @@
     }
 
     @Override
-    public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
-            int userId, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, int statsClient) {
+    public long scheduleFaceDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FaceAuthenticateOptions options, int statsClient) {
         throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
                 + "forget to check the supportsFaceDetection flag?");
     }
@@ -665,7 +665,7 @@
     }
 
     @Override
-    public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter receiver,
             @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
             int statsClient, boolean allowBackgroundAuthentication) {
@@ -675,8 +675,8 @@
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
             final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
-                    mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
-                    options.getOpPackageName(), cookie, false /* requireConfirmation */, mSensorId,
+                    mLazyDaemon, token, requestId, receiver, operationId, restricted,
+                    options, cookie, false /* requireConfirmation */,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric, mLockoutTracker,
                     mUsageStats, allowBackgroundAuthentication,
@@ -686,13 +686,13 @@
     }
 
     @Override
-    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+    public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter receiver,
             @NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         final long id = mRequestCounter.incrementAndGet();
 
-        scheduleAuthenticate(sensorId, token, operationId, cookie, receiver,
+        scheduleAuthenticate(token, operationId, cookie, receiver,
                 options, id, restricted, statsClient, allowBackgroundAuthentication);
 
         return id;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 1c1f56c..8ab8892 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -25,6 +25,7 @@
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -50,7 +51,8 @@
  * Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
  * HIDL interface.
  */
-class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
+class FaceAuthenticationClient
+        extends AuthenticationClient<IBiometricsFace, FaceAuthenticateOptions> {
 
     private static final String TAG = "FaceAuthenticationClient";
 
@@ -67,17 +69,18 @@
     FaceAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
-            boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener, long operationId,
+            boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
+            boolean requireConfirmation,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
             @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
             @Authenticators.Types int sensorStrength) {
-        super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+        super(context, lazyDaemon, token, listener, operationId, restricted,
+                options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */,
                 lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */,
-                 sensorStrength);
+                sensorStrength);
         setRequestId(requestId);
         mUsageStats = usageStats;
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 52d887a..d47a57a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -23,6 +23,7 @@
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IFingerprintService;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -63,8 +64,13 @@
             long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
             String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
             throws RemoteException {
-        mFingerprintService.prepareForAuthentication(mSensorId, token, operationId, userId,
-                sensorReceiver, opPackageName, requestId, cookie, allowBackgroundAuthentication);
+        mFingerprintService.prepareForAuthentication(token, operationId, sensorReceiver,
+                new FingerprintAuthenticateOptions.Builder()
+                        .setSensorId(mSensorId)
+                        .setUserId(userId)
+                        .setOpPackageName(opPackageName)
+                        .build(),
+                requestId, cookie, allowBackgroundAuthentication);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index affc496e..8a33f22 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -119,7 +119,7 @@
     @NonNull
     private final Supplier<String[]> mAidlInstanceNameSupplier;
     @NonNull
-    private final Function<String, IFingerprint> mIFingerprintProvider;
+    private final Function<String, FingerprintProvider> mFingerprintProvider;
     @NonNull
     private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
             mBiometricStateCallback;
@@ -307,6 +307,8 @@
             if (provider == null) {
                 Slog.w(TAG, "Null provider for authenticate");
                 return -1;
+            } else {
+                options.setSensorId(provider.first);
             }
 
             final FingerprintSensorPropertiesInternal sensorProps =
@@ -322,8 +324,8 @@
                     return -1;
                 }
             }
-            return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
-                    0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
+            return provider.second.scheduleAuthenticate(token, operationId,
+                    0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
                     restricted, statsClient, isKeyguard);
         }
 
@@ -436,29 +438,32 @@
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFingerprint");
                 return -1;
+            } else {
+                options.setSensorId(provider.first);
             }
 
-            return provider.second.scheduleFingerDetect(provider.first, token, options.getUserId(),
-                    new ClientMonitorCallbackConverter(receiver), opPackageName,
+            return provider.second.scheduleFingerDetect(token,
+                    new ClientMonitorCallbackConverter(receiver), options,
                     BiometricsProtoEnums.CLIENT_KEYGUARD);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
         @Override // Binder call
-        public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
-                int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
+        public void prepareForAuthentication(IBinder token, long operationId,
+                IBiometricSensorReceiver sensorReceiver,
+                @NonNull FingerprintAuthenticateOptions options,
                 long requestId, int cookie, boolean allowBackgroundAuthentication) {
             super.prepareForAuthentication_enforcePermission();
 
-            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(options.getSensorId());
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
                 return;
             }
 
             final boolean restricted = true; // BiometricPrompt is always restricted
-            provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
-                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
+            provider.scheduleAuthenticate(token, operationId, cookie,
+                    new ClientMonitorCallbackConverter(sensorReceiver), options, requestId,
                     restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
                     allowBackgroundAuthentication);
         }
@@ -982,8 +987,7 @@
                 () -> IBiometricService.Stub.asInterface(
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
                 () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
-                (fqName) -> IFingerprint.Stub.asInterface(
-                        Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))));
+                null /* fingerprintProvider */);
     }
 
     @VisibleForTesting
@@ -991,16 +995,35 @@
             BiometricContext biometricContext,
             Supplier<IBiometricService> biometricServiceSupplier,
             Supplier<String[]> aidlInstanceNameSupplier,
-            Function<String, IFingerprint> fingerprintProvider) {
+            Function<String, FingerprintProvider> fingerprintProvider) {
         super(context);
         mBiometricContext = biometricContext;
         mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
-        mIFingerprintProvider = fingerprintProvider;
         mAppOps = context.getSystemService(AppOpsManager.class);
         mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+        mFingerprintProvider = fingerprintProvider != null ? fingerprintProvider :
+                (name) -> {
+                    final String fqName = IFingerprint.DESCRIPTOR + "/" + name;
+                    final IFingerprint fp = IFingerprint.Stub.asInterface(
+                            Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+                    if (fp != null) {
+                        try {
+                            return new FingerprintProvider(getContext(),
+                                    mBiometricStateCallback, fp.getSensorProps(), name,
+                                    mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
+                                    mBiometricContext);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
+                        }
+                    } else {
+                        Slog.e(TAG, "Unable to get declared service: " + fqName);
+                    }
+
+                    return null;
+                };
         mHandler = new Handler(Looper.getMainLooper());
         mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
         mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1044,23 +1067,9 @@
         final List<ServiceProvider> providers = new ArrayList<>();
 
         for (String instance : instances) {
-            final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
-            final IFingerprint fp = mIFingerprintProvider.apply(fqName);
-
-            if (fp != null) {
-                try {
-                    final FingerprintProvider provider = new FingerprintProvider(getContext(),
-                            mBiometricStateCallback, fp.getSensorProps(), instance,
-                            mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
-                            mBiometricContext);
-                    Slog.i(TAG, "Adding AIDL provider: " + fqName);
-                    providers.add(provider);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
-                }
-            } else {
-                Slog.e(TAG, "Unable to get declared service: " + fqName);
-            }
+            final FingerprintProvider provider = mFingerprintProvider.apply(instance);
+            Slog.i(TAG, "Adding AIDL provider: " + instance);
+            providers.add(provider);
         }
 
         return providers;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 5b6f14d..004af2c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -23,6 +23,7 @@
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -78,19 +79,21 @@
 
     void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
 
-    long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
-            @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
+    long scheduleFingerDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FingerprintAuthenticateOptions options,
             int statsClient);
 
-    void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+    void scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+            @NonNull FingerprintAuthenticateOptions options,
+            long requestId, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication);
 
-    long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+    long scheduleAuthenticate(@NonNull IBinder token, long operationId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication);
+            @NonNull FingerprintAuthenticateOptions options,
+            boolean restricted, int statsClient, boolean allowBackgroundAuthentication);
 
     void startPreparedClient(int sensorId, int cookie);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index d1a7b13..0f81f9f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -26,6 +26,7 @@
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlay;
@@ -65,7 +66,8 @@
  * Fingerprint-specific authentication client supporting the {@link
  * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
  */
-class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
+class FingerprintAuthenticationClient
+        extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions>
         implements Udfps, LockoutConsumer, PowerPressHandler {
     private static final String TAG = "FingerprintAuthenticationClient";
     private static final int MESSAGE_AUTH_SUCCESS = 2;
@@ -97,13 +99,11 @@
             @NonNull IBinder token,
             long requestId,
             @NonNull ClientMonitorCallbackConverter listener,
-            int targetUserId,
             long operationId,
             boolean restricted,
-            @NonNull String owner,
+            @NonNull FingerprintAuthenticateOptions options,
             int cookie,
             boolean requireConfirmation,
-            int sensorId,
             @NonNull BiometricLogger biometricLogger,
             @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric,
@@ -122,13 +122,11 @@
                 lazyDaemon,
                 token,
                 listener,
-                targetUserId,
                 operationId,
                 restricted,
-                owner,
+                options,
                 cookie,
                 requireConfirmation,
-                sensorId,
                 biometricLogger,
                 biometricContext,
                 isStrongBiometric,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index f6911ea..376d231 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
@@ -52,13 +53,14 @@
 
     FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull String owner, int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable IUdfpsOverlay udfpsOverlay,
             boolean isStrongBiometric) {
-        super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+        super(context, lazyDaemon, token, listener, options.getUserId(),
+                options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 776d331..a833278 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -37,6 +37,7 @@
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -422,15 +423,17 @@
     }
 
     @Override
-    public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
-            @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
+    public long scheduleFingerDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FingerprintAuthenticateOptions options,
             int statsClient) {
         final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
+            final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
-                    opPackageName, sensorId,
+                    mSensors.get(sensorId).getLazySession(), token, id, callback,
+                    options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext,
                     mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric);
@@ -441,16 +444,19 @@
     }
 
     @Override
-    public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
-            int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+    public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FingerprintAuthenticateOptions options,
+            long requestId, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         mHandler.post(() -> {
+            final int userId = options.getUserId();
+            final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
-                    userId, operationId, restricted, opPackageName, cookie,
-                    false /* requireConfirmation */, sensorId,
+                    operationId, restricted, options, cookie,
+                    false /* requireConfirmation */,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
@@ -485,14 +491,14 @@
     }
 
     @Override
-    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
-            int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, boolean restricted, int statsClient,
+    public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull FingerprintAuthenticateOptions options, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         final long id = mRequestCounter.incrementAndGet();
 
-        scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
-                opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+        scheduleAuthenticate(token, operationId, cookie, callback,
+                options, id, restricted, statsClient, allowBackgroundAuthentication);
 
         return id;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 4567addc..99c491a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -34,6 +34,7 @@
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -631,17 +632,17 @@
     }
 
     @Override
-    public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
-            @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
+    public long scheduleFingerDetect(@NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
             int statsClient) {
         final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
+            scheduleUpdateActiveUserWithoutHandler(options.getUserId());
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
-                    mLazyDaemon, token, id, listener, userId, opPackageName,
-                    mSensorProperties.sensorId,
+                    mLazyDaemon, token, id, listener, options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay,
                     isStrongBiometric);
@@ -652,18 +653,18 @@
     }
 
     @Override
-    public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
-            int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+    public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
+            long requestId, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         mHandler.post(() -> {
-            scheduleUpdateActiveUserWithoutHandler(userId);
+            scheduleUpdateActiveUserWithoutHandler(options.getUserId());
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
-                    mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
-                    restricted, opPackageName, cookie, false /* requireConfirmation */,
-                    mSensorProperties.sensorId,
+                    mContext, mLazyDaemon, token, requestId, listener, operationId,
+                    restricted, options, cookie, false /* requireConfirmation */,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
@@ -675,14 +676,14 @@
     }
 
     @Override
-    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
-            int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull String opPackageName, boolean restricted, int statsClient,
+    public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
+            int cookie, @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication) {
         final long id = mRequestCounter.incrementAndGet();
 
-        scheduleAuthenticate(sensorId, token, operationId, userId, cookie, listener,
-                opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+        scheduleAuthenticate(token, operationId, cookie, listener,
+                options, id, restricted, statsClient, allowBackgroundAuthentication);
 
         return id;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 73b1288..0a47c12 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -22,6 +22,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
@@ -362,13 +363,16 @@
             // Store the authClient parameters so it can be rescheduled
             final IBinder token = client.getToken();
             final long operationId = authClient.getOperationId();
-            final int user = client.getTargetUserId();
             final int cookie = client.getCookie();
             final ClientMonitorCallbackConverter listener = client.getListener();
-            final String opPackageName = client.getOwnerString();
             final boolean restricted = authClient.isRestricted();
             final int statsClient = client.getLogger().getStatsClient();
             final boolean isKeyguard = authClient.isKeyguard();
+            final FingerprintAuthenticateOptions options =
+                    new FingerprintAuthenticateOptions.Builder()
+                            .setUserId(client.getTargetUserId())
+                            .setOpPackageName(client.getOwnerString())
+                            .build();
 
             // Don't actually send cancel() to the HAL, since successful auth already finishes
             // HAL authenticate() lifecycle. Just
@@ -376,8 +380,8 @@
 
             // Schedule this only after we invoke onClientFinished for the previous client, so that
             // internal preemption logic is not run.
-            mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token,
-                    operationId, user, cookie, listener, opPackageName, restricted, statsClient,
+            mFingerprint21.scheduleAuthenticate(token,
+                    operationId, cookie, listener, options, restricted, statsClient,
                     isKeyguard);
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 957005a..d22aef8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -26,6 +26,7 @@
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlay;
@@ -57,7 +58,8 @@
  * {@link android.hardware.biometrics.fingerprint.V2_1} and
  * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
  */
-class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFingerprint>
+class FingerprintAuthenticationClient
+        extends AuthenticationClient<IBiometricsFingerprint, FingerprintAuthenticateOptions>
         implements Udfps {
 
     private static final String TAG = "Biometrics/FingerprintAuthClient";
@@ -72,9 +74,9 @@
     FingerprintAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
-            boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
-            int sensorId, @NonNull BiometricLogger logger,
+            @NonNull ClientMonitorCallbackConverter listener, long operationId,
+            boolean restricted, @NonNull FingerprintAuthenticateOptions options,
+            int cookie, boolean requireConfirmation, @NonNull BiometricLogger logger,
             @NonNull BiometricContext biometricContext, boolean isStrongBiometric,
             @NonNull TaskStackListener taskStackListener,
             @NonNull LockoutFrameworkImpl lockoutTracker,
@@ -84,8 +86,8 @@
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Authenticators.Types int sensorStrength) {
-        super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
-                owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+        super(context, lazyDaemon, token, listener, operationId, restricted,
+                options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
                 false /* shouldVibrate */, sensorStrength);
         setRequestId(requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index cfa9fb4..362c820 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -24,6 +24,7 @@
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
@@ -61,12 +62,13 @@
     public FingerprintDetectClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
             @NonNull IBinder token, long requestId,
-            @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
-            int sensorId,
+            @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull FingerprintAuthenticateOptions options,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) {
-        super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+        super(context, lazyDaemon, token, listener, options.getUserId(),
+                options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mSensorOverlays = new SensorOverlays(udfpsOverlayController,
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index f7183e6..dd92ffc 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -484,6 +484,51 @@
                     sourcePackage);
         }
 
+        @Override
+        public boolean areClipboardAccessNotificationsEnabledForUser(int userId) {
+            int result = getContext().checkCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION);
+            if (result != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("areClipboardAccessNotificationsEnable requires "
+                        + "permission MANAGE_CLIPBOARD_ACCESS_NOTIFICATION");
+            }
+
+            long callingId = Binder.clearCallingIdentity();
+            try {
+                return Settings.Secure.getIntForUser(getContext().getContentResolver(),
+                        Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
+                        getDefaultClipboardAccessNotificationsSetting(), userId) != 0;
+            } finally {
+                Binder.restoreCallingIdentity(callingId);
+            }
+        }
+
+        @Override
+        public void setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId) {
+            int result = getContext().checkCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION);
+            if (result != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("areClipboardAccessNotificationsEnable requires "
+                        + "permission MANAGE_CLIPBOARD_ACCESS_NOTIFICATION");
+            }
+
+            long callingId = Binder.clearCallingIdentity();
+            try {
+                ContentResolver resolver = getContext()
+                        .createContextAsUser(UserHandle.of(userId), 0).getContentResolver();
+                Settings.Secure.putInt(resolver,
+                        Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, (enable ? 1 : 0));
+            } finally {
+                Binder.restoreCallingIdentity(callingId);
+            }
+        }
+
+        private int getDefaultClipboardAccessNotificationsSetting() {
+            return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
+                    ClipboardManager.DEVICE_CONFIG_SHOW_ACCESS_NOTIFICATIONS,
+                    ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS) ? 1 : 0;
+        }
+
         private void checkAndSetPrimaryClip(
                 ClipData clip,
                 String callingPackage,
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index ab2c002..1ce917c 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3254,6 +3254,8 @@
             }
 
             mActiveNetwork = network;
+            mUnderlyingLinkProperties = null;
+            mUnderlyingNetworkCapabilities = null;
             mRetryCount = 0;
 
             startOrMigrateIkeSession(network);
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
index b0dfb84..4eefe5c 100644
--- a/services/core/java/com/android/server/cpu/CpuMonitorService.java
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -38,7 +38,7 @@
 /** Service to monitor CPU availability and usage. */
 public final class CpuMonitorService extends SystemService {
     static final String TAG = CpuMonitorService.class.getSimpleName();
-    static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG);
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     // TODO(b/242722241): Make this a resource overlay property.
     //  Maintain 3 monitoring intervals:
     //  * One to poll very frequently when mCpuAvailabilityCallbackInfoByCallbacks are available and
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d4877eb..9917bfc 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1886,10 +1886,6 @@
                     ? BrightnessEvent.FLAG_USER_SET : 0));
             Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
 
-            // Log all events which are not temporary
-            if (newEvent.getReason().getReason() != BrightnessReason.REASON_TEMPORARY) {
-                logBrightnessEvent(newEvent, unthrottledBrightnessState);
-            }
             if (userSetBrightnessChanged) {
                 logManualBrightnessEvent(newEvent);
             }
@@ -3002,154 +2998,6 @@
         }
     }
 
-    // Return bucket index of range_[left]_[right] where
-    // left <= nits < right
-    private int nitsToRangeIndex(float nits) {
-        float[] boundaries = {
-            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
-            90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
-            1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000};
-        int[] rangeIndex = {
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_UNKNOWN,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_0_1,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1_2,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2_3,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_3_4,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_4_5,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_5_6,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_6_7,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_7_8,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_8_9,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_9_10,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_10_20,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_20_30,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_30_40,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_40_50,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_50_60,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_60_70,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_70_80,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_80_90,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_90_100,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_100_200,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_200_300,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_300_400,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_400_500,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_500_600,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_600_700,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_700_800,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_800_900,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_900_1000,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1000_1200,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1200_1400,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1400_1600,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1600_1800,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1800_2000,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2000_2250,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2250_2500,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2500_2750,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2750_3000,
-        };
-        for (int i = 0; i < boundaries.length; i++) {
-            if (nits < boundaries[i]) {
-                return rangeIndex[i];
-            }
-        }
-        return FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_3000_INF;
-    }
-
-    private int convertBrightnessReasonToStatsEnum(int brightnessReason) {
-        switch(brightnessReason) {
-            case BrightnessReason.REASON_UNKNOWN:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_UNKNOWN;
-            case BrightnessReason.REASON_MANUAL:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_MANUAL;
-            case BrightnessReason.REASON_DOZE:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_DOZE;
-            case BrightnessReason.REASON_DOZE_DEFAULT:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_DOZE_DEFAULT;
-            case BrightnessReason.REASON_AUTOMATIC:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_AUTOMATIC;
-            case BrightnessReason.REASON_SCREEN_OFF:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_SCREEN_OFF;
-            case BrightnessReason.REASON_OVERRIDE:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_OVERRIDE;
-            case BrightnessReason.REASON_TEMPORARY:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_TEMPORARY;
-            case BrightnessReason.REASON_BOOST:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_BOOST;
-            case BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_SCREEN_OFF_BRIGHTNESS_SENSOR;
-            case BrightnessReason.REASON_FOLLOWER:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_FOLLOWER;
-        }
-        return FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_UNKNOWN;
-    }
-
-    private int convertHbmModeToStatsEnum(int mode) {
-        switch(mode) {
-            case BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_OFF;
-            case BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_SUNLIGHT;
-            case BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_HDR;
-        }
-        return FrameworkStatsLog
-            .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_OFF;
-    }
-
-    // unmodifiedBrightness: the brightness value that has not been
-    // modified by any modifiers(dimming/throttling/low-power-mode)
-    private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness) {
-        int modifier = event.getReason().getModifier();
-        // It's easier to check if the brightness is at maximum level using the brightness
-        // value untouched by any modifiers
-        boolean brightnessIsMax = unmodifiedBrightness == event.getHbmMax();
-        float brightnessInNits = convertToNits(event.getBrightness());
-        float lowPowerModeFactor = event.getPowerFactor();
-        int rbcStrength  = event.getRbcStrength();
-        float hbmMaxNits = convertToNits(event.getHbmMax());
-        float thermalCapNits = convertToNits(event.getThermalMax());
-
-        if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
-                && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
-                    .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
-            FrameworkStatsLog.write(FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2,
-                    event.getPhysicalDisplayId().hashCode(),
-                    brightnessInNits,
-                    convertToNits(unmodifiedBrightness),
-                    nitsToRangeIndex(brightnessInNits),
-                    brightnessIsMax,
-                    (event.getFlags() & BrightnessEvent.FLAG_USER_SET) > 0,
-                    convertBrightnessReasonToStatsEnum(event.getReason().getReason()),
-                    convertHbmModeToStatsEnum(event.getHbmMode()),
-                    (modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
-                    (modifier & BrightnessReason.MODIFIER_THROTTLED) > 0,
-                    event.isRbcEnabled(),
-                    event.getLux(),
-                    event.wasShortTermModelActive(),
-                    lowPowerModeFactor,
-                    rbcStrength,
-                    hbmMaxNits,
-                    thermalCapNits,
-                    event.isAutomaticBrightnessEnabled());
-        }
-    }
-
     private void logManualBrightnessEvent(BrightnessEvent event) {
         float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
         int appliedRbcStrength  = event.isRbcEnabled() ? event.getRbcStrength() : -1;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index a928777..f49419cf 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1148,6 +1148,7 @@
         final int previousPolicy;
         boolean mustInitialize = false;
         int brightnessAdjustmentFlags = 0;
+        mBrightnessReasonTemp.set(null);
         mTempBrightnessEvent.reset();
         SparseArray<DisplayPowerControllerInterface> displayBrightnessFollowers;
         synchronized (mLock) {
@@ -1601,10 +1602,6 @@
                     ? BrightnessEvent.FLAG_USER_SET : 0));
             Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
 
-            // Log all events which are not temporary
-            if (newEvent.getReason().getReason() != BrightnessReason.REASON_TEMPORARY) {
-                logBrightnessEvent(newEvent, unthrottledBrightnessState);
-            }
             if (userSetBrightnessChanged) {
                 logManualBrightnessEvent(newEvent);
             }
@@ -2541,154 +2538,6 @@
         return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
     }
 
-    // Return bucket index of range_[left]_[right] where
-    // left <= nits < right
-    private int nitsToRangeIndex(float nits) {
-        float[] boundaries = {
-            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
-            90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
-            1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000};
-        int[] rangeIndex = {
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_UNKNOWN,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_0_1,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1_2,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2_3,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_3_4,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_4_5,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_5_6,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_6_7,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_7_8,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_8_9,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_9_10,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_10_20,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_20_30,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_30_40,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_40_50,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_50_60,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_60_70,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_70_80,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_80_90,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_90_100,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_100_200,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_200_300,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_300_400,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_400_500,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_500_600,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_600_700,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_700_800,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_800_900,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_900_1000,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1000_1200,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1200_1400,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1400_1600,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1600_1800,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1800_2000,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2000_2250,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2250_2500,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2500_2750,
-            FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2750_3000,
-        };
-        for (int i = 0; i < boundaries.length; i++) {
-            if (nits < boundaries[i]) {
-                return rangeIndex[i];
-            }
-        }
-        return FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_3000_INF;
-    }
-
-    private int convertBrightnessReasonToStatsEnum(int brightnessReason) {
-        switch(brightnessReason) {
-            case BrightnessReason.REASON_UNKNOWN:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_UNKNOWN;
-            case BrightnessReason.REASON_MANUAL:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_MANUAL;
-            case BrightnessReason.REASON_DOZE:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_DOZE;
-            case BrightnessReason.REASON_DOZE_DEFAULT:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_DOZE_DEFAULT;
-            case BrightnessReason.REASON_AUTOMATIC:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_AUTOMATIC;
-            case BrightnessReason.REASON_SCREEN_OFF:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_SCREEN_OFF;
-            case BrightnessReason.REASON_OVERRIDE:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_OVERRIDE;
-            case BrightnessReason.REASON_TEMPORARY:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_TEMPORARY;
-            case BrightnessReason.REASON_BOOST:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_BOOST;
-            case BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_SCREEN_OFF_BRIGHTNESS_SENSOR;
-            case BrightnessReason.REASON_FOLLOWER:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_FOLLOWER;
-        }
-        return FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_UNKNOWN;
-    }
-
-    private int convertHbmModeToStatsEnum(int mode) {
-        switch(mode) {
-            case BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_OFF;
-            case BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_SUNLIGHT;
-            case BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR:
-                return FrameworkStatsLog
-                    .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_HDR;
-        }
-        return FrameworkStatsLog
-            .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_OFF;
-    }
-
-    // unmodifiedBrightness: the brightness value that has not been
-    // modified by any modifiers(dimming/throttling/low-power-mode)
-    private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness) {
-        int modifier = event.getReason().getModifier();
-        // It's easier to check if the brightness is at maximum level using the brightness
-        // value untouched by any modifiers
-        boolean brightnessIsMax = unmodifiedBrightness == event.getHbmMax();
-        float brightnessInNits = convertToNits(event.getBrightness());
-        float lowPowerModeFactor = event.getPowerFactor();
-        int rbcStrength  = event.getRbcStrength();
-        float hbmMaxNits = convertToNits(event.getHbmMax());
-        float thermalCapNits = convertToNits(event.getThermalMax());
-
-        if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
-                && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
-                    .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
-            FrameworkStatsLog.write(FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2,
-                    event.getPhysicalDisplayId().hashCode(),
-                    brightnessInNits,
-                    convertToNits(unmodifiedBrightness),
-                    nitsToRangeIndex(brightnessInNits),
-                    brightnessIsMax,
-                    (event.getFlags() & BrightnessEvent.FLAG_USER_SET) > 0,
-                    convertBrightnessReasonToStatsEnum(event.getReason().getReason()),
-                    convertHbmModeToStatsEnum(event.getHbmMode()),
-                    (modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
-                    (modifier & BrightnessReason.MODIFIER_THROTTLED) > 0,
-                    event.isRbcEnabled(),
-                    event.getLux(),
-                    event.wasShortTermModelActive(),
-                    lowPowerModeFactor,
-                    rbcStrength,
-                    hbmMaxNits,
-                    thermalCapNits,
-                    event.isAutomaticBrightnessEnabled());
-        }
-    }
-
     private final class DisplayControllerHandler extends Handler {
         DisplayControllerHandler(Looper looper) {
             super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS
index 8e34601..fcaa957 100644
--- a/services/core/java/com/android/server/display/OWNERS
+++ b/services/core/java/com/android/server/display/OWNERS
@@ -4,5 +4,7 @@
 ogunwale@google.com
 santoscordon@google.com
 flc@google.com
+wilczynskip@google.com
+brup@google.com
 
 per-file ColorDisplayService.java=christyfranks@google.com
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index 8b09571..aff80de 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -100,7 +100,7 @@
      * A utility to reset the BrightnessEvent to default values
      */
     public void reset() {
-        mReason.set(null);
+        mReason = new BrightnessReason();
         mTime = SystemClock.uptimeMillis();
         mPhysicalDisplayId = "";
         // Lux values
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
index c411319..1153cc3 100644
--- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
+++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.health.BatteryHealthData;
 import android.hardware.health.HealthInfo;
 import android.hardware.health.IHealth;
 import android.os.BatteryManager;
@@ -113,6 +114,7 @@
     private int getPropertyInternal(int id, BatteryProperty prop) throws RemoteException {
         IHealth service = mLastService.get();
         if (service == null) throw new RemoteException("no health service");
+        BatteryHealthData healthData;
         try {
             switch (id) {
                 case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
@@ -133,6 +135,21 @@
                 case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
                     prop.setLong(service.getEnergyCounterNwh());
                     break;
+                case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
+                    healthData = service.getBatteryHealthData();
+                    prop.setLong(healthData.batteryManufacturingDateSeconds);
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
+                    healthData = service.getBatteryHealthData();
+                    prop.setLong(healthData.batteryFirstUsageSeconds);
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
+                    prop.setLong(service.getChargingPolicy());
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH:
+                    healthData = service.getBatteryHealthData();
+                    prop.setLong(healthData.batteryStateOfHealth);
+                    break;
             }
         } catch (UnsupportedOperationException e) {
             // Leave prop untouched.
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 9e8b9f1..e0253fc 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -343,9 +343,9 @@
     private void visitAllKeyboardLayouts(KeyboardLayoutVisitor visitor) {
         final PackageManager pm = mContext.getPackageManager();
         Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS);
-        for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent,
+        for (ResolveInfo resolveInfo : pm.queryBroadcastReceiversAsUser(intent,
                 PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE)) {
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, UserHandle.USER_SYSTEM)) {
             final ActivityInfo activityInfo = resolveInfo.activityInfo;
             final int priority = resolveInfo.priority;
             visitKeyboardLayoutsInPackage(pm, activityInfo, null, priority, visitor);
@@ -464,7 +464,7 @@
         return LocaleList.forLanguageTags(languageTags.replace('|', ','));
     }
 
-    private static String getLayoutDescriptor(@NonNull InputDeviceIdentifier identifier) {
+    private String getLayoutDescriptor(@NonNull InputDeviceIdentifier identifier) {
         Objects.requireNonNull(identifier, "identifier must not be null");
         Objects.requireNonNull(identifier.getDescriptor(), "descriptor must not be null");
 
@@ -474,7 +474,21 @@
         // If vendor id and product id is available, use it as keys. This allows us to have the
         // same setup for all keyboards with same product and vendor id. i.e. User can swap 2
         // identical keyboards and still get the same setup.
-        return "vendor:" + identifier.getVendorId() + ",product:" + identifier.getProductId();
+        StringBuilder key = new StringBuilder();
+        key.append("vendor:").append(identifier.getVendorId()).append(",product:").append(
+                identifier.getProductId());
+
+        InputDevice inputDevice = getInputDevice(identifier);
+        Objects.requireNonNull(inputDevice, "Input device must not be null");
+        // Some keyboards can have same product ID and vendor ID but different Keyboard info like
+        // language tag and layout type.
+        if (!TextUtils.isEmpty(inputDevice.getKeyboardLanguageTag())) {
+            key.append(",languageTag:").append(inputDevice.getKeyboardLanguageTag());
+        }
+        if (!TextUtils.isEmpty(inputDevice.getKeyboardLayoutType())) {
+            key.append(",layoutType:").append(inputDevice.getKeyboardLanguageTag());
+        }
+        return key.toString();
     }
 
     @Nullable
@@ -1075,7 +1089,7 @@
                 identifier.getDescriptor()) : null;
     }
 
-    private static String createLayoutKey(InputDeviceIdentifier identifier, int userId,
+    private String createLayoutKey(InputDeviceIdentifier identifier, int userId,
             @NonNull InputMethodSubtypeHandle subtypeHandle) {
         Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null");
         return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + userId
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 1c7294f..eb4dba6 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -20,12 +20,14 @@
 
 import android.Manifest;
 import android.annotation.AnyThread;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.UiThread;
 import android.hardware.input.InputManager;
 import android.os.IBinder;
 import android.os.Looper;
+import android.text.TextUtils;
 import android.util.Slog;
 import android.view.BatchedInputEventReceiver;
 import android.view.Choreographer;
@@ -35,6 +37,8 @@
 import android.view.InputEventReceiver;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
 
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
@@ -52,6 +56,9 @@
     // TODO(b/210039666): flip the flag.
     static final boolean DEBUG = true;
     private static final int EVENT_BUFFER_SIZE = 100;
+    // A longer event buffer used for handwriting delegation
+    // TODO(b/210039666): make this device touch sampling rate dependent.
+    private static final int LONG_EVENT_BUFFER = EVENT_BUFFER_SIZE * 20;
 
     // This must be the looper for the UiThread.
     private final Looper mLooper;
@@ -63,6 +70,9 @@
     private Runnable mInkWindowInitRunnable;
     private boolean mRecordingGesture;
     private int mCurrentDisplayId;
+    // when set, package names are used for handwriting delegation.
+    private @Nullable String mDelegatePackageName;
+    private @Nullable String mDelegatorPackageName;
 
     private HandwritingEventReceiverSurface mHandwritingSurface;
 
@@ -137,6 +147,41 @@
         return mRecordingGesture;
     }
 
+    boolean hasOngoingStylusHandwritingSession() {
+        return mHandwritingSurface != null && mHandwritingSurface.isIntercepting();
+    }
+
+    /**
+     * Prepare delegation of stylus handwriting to a different editor
+     * @see InputMethodManager#prepareStylusHandwritingDelegation(View, String)
+     */
+    void prepareStylusHandwritingDelegation(
+            @NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
+        mDelegatePackageName = delegatePackageName;
+        mDelegatorPackageName = delegatorPackageName;
+        ((ArrayList) mHandwritingBuffer).ensureCapacity(LONG_EVENT_BUFFER);
+        // TODO(b/210039666): cancel delegation after a timeout or next input method client binding.
+    }
+
+    @Nullable String getDelegatePackageName() {
+        return mDelegatePackageName;
+    }
+
+    @Nullable String getDelegatorPackageName() {
+        return mDelegatorPackageName;
+    }
+
+    /**
+     * Clear any pending handwriting delegation info.
+     */
+    void clearPendingHandwritingDelegation() {
+        if (DEBUG) {
+            Slog.d(TAG, "clearPendingHandwritingDelegation");
+        }
+        mDelegatorPackageName = null;
+        mDelegatePackageName = null;
+    }
+
     /**
      * Starts a {@link HandwritingSession} to transfer to the IME.
      *
@@ -223,6 +268,7 @@
             }
         }
 
+        clearPendingHandwritingDelegation();
         mRecordingGesture = false;
     }
 
@@ -259,7 +305,10 @@
             mInkWindowInitRunnable = null;
         }
 
-        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+        // If handwriting delegation is ongoing, don't clear the buffer so that multiple strokes
+        // can be buffered across windows.
+        if (TextUtils.isEmpty(mDelegatePackageName)
+                && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) {
             mRecordingGesture = false;
             mHandwritingBuffer.clear();
             return;
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index da65f27..2efb0be 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -24,13 +24,14 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.util.Log;
 import android.view.inputmethod.ImeTracker;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.view.IImeTracker;
 
 import java.io.PrintWriter;
 import java.time.Instant;
@@ -53,7 +54,7 @@
 @SuppressWarnings("GuardedBy")
 public final class ImeTrackerService extends IImeTracker.Stub {
 
-    static final String TAG = "ImeTrackerService";
+    private static final String TAG = ImeTracker.TAG;
 
     /** The threshold in milliseconds after which a history entry is considered timed out. */
     private static final long TIMEOUT_MS = 10_000;
@@ -71,67 +72,71 @@
 
     @NonNull
     @Override
-    public synchronized IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
-        final IBinder binder = new Binder();
-        final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_SHOW,
-                ImeTracker.STATUS_RUN, origin, reason);
+    public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+        final var binder = new Binder();
+        final var token = new ImeTracker.Token(binder, tag);
+        final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN,
+                origin, reason);
         mHistory.addEntry(binder, entry);
 
         // Register a delayed task to handle the case where the new entry times out.
         mHandler.postDelayed(() -> {
             synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
             }
         }, TIMEOUT_MS);
 
-        return binder;
+        return token;
     }
 
     @NonNull
     @Override
-    public synchronized IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
-            @SoftInputShowHideReason int reason) {
-        final IBinder binder = new Binder();
-        final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_HIDE,
-                ImeTracker.STATUS_RUN, origin, reason);
+    public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+        final var binder = new Binder();
+        final var token = new ImeTracker.Token(binder, tag);
+        final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN,
+                origin, reason);
         mHistory.addEntry(binder, entry);
 
         // Register a delayed task to handle the case where the new entry times out.
         mHandler.postDelayed(() -> {
             synchronized (ImeTrackerService.this) {
-                mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
             }
         }, TIMEOUT_MS);
 
-        return binder;
+        return token;
     }
 
     @Override
-    public synchronized void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
-        final History.Entry entry = mHistory.getEntry(statsToken);
+    public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
+        final var entry = mHistory.getEntry(binder);
         if (entry == null) return;
 
         entry.mPhase = phase;
     }
 
     @Override
-    public synchronized void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    public synchronized void onFailed(@NonNull ImeTracker.Token statsToken,
+            @ImeTracker.Phase int phase) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
     }
 
     @Override
-    public synchronized void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+    public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken,
+            @ImeTracker.Phase int phase) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
     }
 
     @Override
-    public synchronized void onShown(@NonNull IBinder statsToken) {
+    public synchronized void onShown(@NonNull ImeTracker.Token statsToken) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
     }
 
     @Override
-    public synchronized void onHidden(@NonNull IBinder statsToken) {
+    public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) {
         mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
     }
 
@@ -141,9 +146,9 @@
      * @param statsToken the token corresponding to the current IME request.
      * @param requestWindowName the name of the window that created the IME request.
      */
-    public synchronized void onImmsUpdate(@NonNull IBinder statsToken,
+    public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
             @NonNull String requestWindowName) {
-        final History.Entry entry = mHistory.getEntry(statsToken);
+        final var entry = mHistory.getEntry(statsToken.getBinder());
         if (entry == null) return;
 
         entry.mRequestWindowName = requestWindowName;
@@ -181,17 +186,17 @@
         /** Latest entry sequence number. */
         private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
 
-        /** Adds a live entry. */
+        /** Adds a live entry corresponding to the given IME tracking token's binder. */
         @GuardedBy("ImeTrackerService.this")
-        private void addEntry(@NonNull IBinder statsToken, @NonNull Entry entry) {
-            mLiveEntries.put(statsToken, entry);
+        private void addEntry(@NonNull IBinder binder, @NonNull Entry entry) {
+            mLiveEntries.put(binder, entry);
         }
 
-        /** Gets the entry corresponding to the given IME tracking token, if it exists. */
+        /** Gets the entry corresponding to the given IME tracking token's binder, if it exists. */
         @Nullable
         @GuardedBy("ImeTrackerService.this")
-        private Entry getEntry(@NonNull IBinder statsToken) {
-            return mLiveEntries.get(statsToken);
+        private Entry getEntry(@NonNull IBinder binder) {
+            return mLiveEntries.get(binder);
         }
 
         /**
@@ -204,10 +209,21 @@
          *              (or {@link ImeTracker#PHASE_NOT_SET} otherwise).
          */
         @GuardedBy("ImeTrackerService.this")
-        private void setFinished(@NonNull IBinder statsToken, @ImeTracker.Status int status,
-                @ImeTracker.Phase int phase) {
-            final Entry entry = mLiveEntries.remove(statsToken);
-            if (entry == null) return;
+        private void setFinished(@NonNull ImeTracker.Token statsToken,
+                @ImeTracker.Status int status, @ImeTracker.Phase int phase) {
+            final var entry = mLiveEntries.remove(statsToken.getBinder());
+            if (entry == null) {
+                // This will be unconditionally called through the postDelayed above to handle
+                // potential timeouts, and is thus intentionally dropped to avoid having to manually
+                // save and remove the registered callback. Only timeout calls are expected.
+                if (status != ImeTracker.STATUS_TIMEOUT) {
+                    Log.i(TAG, statsToken.getTag()
+                            + ": setFinished on previously finished token at "
+                            + ImeTracker.Debug.phaseToString(phase) + " with "
+                            + ImeTracker.Debug.statusToString(status));
+                }
+                return;
+            }
 
             entry.mDuration = System.currentTimeMillis() - entry.mStartTime;
             entry.mStatus = status;
@@ -216,6 +232,13 @@
                 entry.mPhase = phase;
             }
 
+            if (status == ImeTracker.STATUS_TIMEOUT) {
+                // All events other than timeouts are already logged in the client-side ImeTracker.
+                Log.i(TAG, statsToken.getTag() + ": setFinished at "
+                        + ImeTracker.Debug.phaseToString(entry.mPhase) + " with "
+                        + ImeTracker.Debug.statusToString(status));
+            }
+
             // Remove excess entries overflowing capacity (plus one for the new entry).
             while (mEntries.size() >= CAPACITY) {
                 mEntries.remove();
@@ -232,21 +255,22 @@
         /** Dumps the contents of the circular buffer. */
         @GuardedBy("ImeTrackerService.this")
         private void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final DateTimeFormatter formatter =
-                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
-                            .withZone(ZoneId.systemDefault());
+            final var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                    .withZone(ZoneId.systemDefault());
 
             pw.print(prefix);
-            pw.println("ImeTrackerService#History.mLiveEntries:");
+            pw.println("ImeTrackerService#History.mLiveEntries: "
+                    + mLiveEntries.size() + " elements");
 
-            for (final Entry entry: mLiveEntries.values()) {
+            for (final var entry: mLiveEntries.values()) {
                 dumpEntry(entry, pw, prefix, formatter);
             }
 
             pw.print(prefix);
-            pw.println("ImeTrackerService#History.mEntries:");
+            pw.println("ImeTrackerService#History.mEntries: "
+                    + mEntries.size() + " elements");
 
-            for (final Entry entry: mEntries) {
+            for (final var entry: mEntries) {
                 dumpEntry(entry, pw, prefix, formatter);
             }
         }
@@ -255,34 +279,22 @@
         private void dumpEntry(@NonNull Entry entry, @NonNull PrintWriter pw,
                 @NonNull String prefix, @NonNull DateTimeFormatter formatter) {
             pw.print(prefix);
-            pw.println("ImeTrackerService#History #" + entry.mSequenceNumber + ":");
+            pw.print(" #" + entry.mSequenceNumber);
+            pw.print(" " + ImeTracker.Debug.typeToString(entry.mType));
+            pw.print(" - " + ImeTracker.Debug.statusToString(entry.mStatus));
+            pw.print(" - " + entry.mTag);
+            pw.println(" (" + entry.mDuration + "ms):");
 
             pw.print(prefix);
-            pw.println(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+            pw.print("   startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+            pw.println(" " + ImeTracker.Debug.originToString(entry.mOrigin));
 
             pw.print(prefix);
-            pw.println(" duration=" + entry.mDuration + "ms");
+            pw.print("   reason=" + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
+            pw.println(" " + ImeTracker.Debug.phaseToString(entry.mPhase));
 
             pw.print(prefix);
-            pw.print(" type=" + ImeTracker.Debug.typeToString(entry.mType));
-
-            pw.print(prefix);
-            pw.print(" status=" + ImeTracker.Debug.statusToString(entry.mStatus));
-
-            pw.print(prefix);
-            pw.print(" origin="
-                    + ImeTracker.Debug.originToString(entry.mOrigin));
-
-            pw.print(prefix);
-            pw.print(" reason="
-                    + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
-
-            pw.print(prefix);
-            pw.print(" phase="
-                    + ImeTracker.Debug.phaseToString(entry.mPhase));
-
-            pw.print(prefix);
-            pw.print(" requestWindowName=" + entry.mRequestWindowName);
+            pw.println("   requestWindowName=" + entry.mRequestWindowName);
         }
 
         /** A history entry. */
@@ -291,6 +303,10 @@
             /** The entry's sequence number in the history. */
             private final int mSequenceNumber = sSequenceNumber.getAndIncrement();
 
+            /** Logging tag, of the shape "component:random_hexadecimal". */
+            @NonNull
+            private final String mTag;
+
             /** Uid of the client that requested the IME. */
             private final int mUid;
 
@@ -323,13 +339,15 @@
             /**
              * Name of the window that created the IME request.
              *
-             * Note: This is later set through {@link #onImmsUpdate(IBinder, String)}.
+             * Note: This is later set through {@link #onImmsUpdate}.
              */
             @NonNull
             private String mRequestWindowName = "not set";
 
-            private Entry(int uid, @ImeTracker.Type int type, @ImeTracker.Status int status,
-                    @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+            private Entry(@NonNull String tag, int uid, @ImeTracker.Type int type,
+                    @ImeTracker.Status int status, @ImeTracker.Origin int origin,
+                    @SoftInputShowHideReason int reason) {
+                mTag = tag;
                 mUid = uid;
                 mType = type;
                 mStatus = status;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f5875ab..f142293 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -145,6 +145,7 @@
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.inputmethod.DirectBootAwareness;
 import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IImeTracker;
 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.IInputContentUriToken;
 import com.android.internal.inputmethod.IInputMethod;
@@ -168,7 +169,6 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
-import com.android.internal.view.IImeTracker;
 import com.android.internal.view.IInputMethodManager;
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.EventLogTags;
@@ -236,6 +236,8 @@
     private static final int MSG_FINISH_HANDWRITING = 1110;
     private static final int MSG_REMOVE_HANDWRITING_WINDOW = 1120;
 
+    private static final int MSG_PREPARE_HANDWRITING_DELEGATION = 1130;
+
     private static final int MSG_SET_INTERACTIVE = 3030;
 
     private static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
@@ -530,6 +532,7 @@
     /**
      * The client that is currently bound to an input method.
      */
+    @Nullable
     private ClientState mCurClient;
 
     /**
@@ -555,11 +558,26 @@
     int mCurFocusedWindowSoftInputMode;
 
     /**
-     * The client by which {@link #mCurFocusedWindow} was reported.
+     * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an
+     * IME-focusable window gained focus (without necessarily starting an input connection),
+     * while {@link #mCurClient} only gets updated when we actually start an input connection.
+     *
+     * @see #mCurFocusedWindow
      */
+    @Nullable
     ClientState mCurFocusedWindowClient;
 
     /**
+     * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from
+     * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs
+     * from {@link #mCurClient}.
+     *
+     * @see #mCurFocusedWindow
+     */
+    @Nullable
+    EditorInfo mCurFocusedWindowEditorInfo;
+
+    /**
      * The {@link IRemoteInputConnection} last provided by the current client.
      */
     IRemoteInputConnection mCurInputConnection;
@@ -578,6 +596,7 @@
     /**
      * The {@link EditorInfo} last provided by the current client.
      */
+    @Nullable
     EditorInfo mCurEditorInfo;
 
     /**
@@ -2263,6 +2282,7 @@
                 }
                 if (mCurFocusedWindowClient == cs) {
                     mCurFocusedWindowClient = null;
+                    mCurFocusedWindowEditorInfo = null;
                 }
             }
         }
@@ -2689,6 +2709,14 @@
     }
 
     @AnyThread
+    void schedulePrepareStylusHandwritingDelegation(
+            @NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
+        mHandler.obtainMessage(
+                MSG_PREPARE_HANDWRITING_DELEGATION,
+                new Pair<>(delegatePackageName, delegatorPackageName)).sendToTarget();
+    }
+
+    @AnyThread
     void scheduleRemoveStylusHandwritingWindow() {
         mHandler.obtainMessage(MSG_REMOVE_HANDWRITING_WINDOW).sendToTarget();
     }
@@ -3304,6 +3332,7 @@
                     "InputMethodManagerService#startStylusHandwriting");
             int uid = Binder.getCallingUid();
             synchronized (ImfLock.class) {
+                mHwController.clearPendingHandwritingDelegation();
                 if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting",
                         null /* statsToken */)) {
                     return;
@@ -3331,6 +3360,13 @@
                                 "There is no ongoing stylus gesture to start stylus handwriting.");
                         return;
                     }
+                    if (mHwController.hasOngoingStylusHandwritingSession()) {
+                        // prevent duplicate calls to startStylusHandwriting().
+                        Slog.e(TAG,
+                                "Stylus handwriting session is already ongoing."
+                                        + " Ignoring startStylusHandwriting().");
+                        return;
+                    }
                     if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started");
                     final IInputMethodInvoker curMethod = getCurMethodLocked();
                     if (curMethod != null) {
@@ -3345,6 +3381,68 @@
         }
     }
 
+    @Override
+    public void prepareStylusHandwritingDelegation(
+            @NonNull IInputMethodClient client,
+            @NonNull String delegatePackageName,
+            @NonNull String delegatorPackageName) {
+        if (!verifyClientAndPackageMatch(client, delegatorPackageName)) {
+            Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
+            throw new IllegalArgumentException("Delegator doesn't match Uid");
+        }
+        schedulePrepareStylusHandwritingDelegation(delegatePackageName, delegatorPackageName);
+    }
+
+    @Override
+    public boolean acceptStylusHandwritingDelegation(
+            @NonNull IInputMethodClient client,
+            @NonNull String delegatePackageName,
+            @NonNull String delegatorPackageName) {
+        if (!verifyDelegator(client, delegatePackageName, delegatorPackageName)) {
+            return false;
+        }
+
+        startStylusHandwriting(client);
+        return true;
+    }
+
+    private boolean verifyClientAndPackageMatch(
+            @NonNull IInputMethodClient client, @NonNull String packageName) {
+        ClientState cs;
+        synchronized (ImfLock.class) {
+            cs = mClients.get(client.asBinder());
+        }
+        if (cs == null) {
+            throw new IllegalArgumentException("unknown client " + client.asBinder());
+        }
+        return InputMethodUtils.checkIfPackageBelongsToUid(
+                mPackageManagerInternal, cs.mUid, packageName);
+    }
+
+    private boolean verifyDelegator(
+            @NonNull IInputMethodClient client,
+            @NonNull String delegatePackageName,
+            @NonNull String delegatorPackageName) {
+        if (!verifyClientAndPackageMatch(client, delegatePackageName)) {
+            Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
+                    + " startStylusHandwriting");
+            return false;
+        }
+        synchronized (ImfLock.class) {
+            if (!delegatorPackageName.equals(mHwController.getDelegatorPackageName())) {
+                Slog.w(TAG,
+                        "Delegator package does not match. Ignoring startStylusHandwriting");
+                return false;
+            }
+            if (!delegatePackageName.equals(mHwController.getDelegatePackageName())) {
+                Slog.w(TAG,
+                        "Delegate package does not match. Ignoring startStylusHandwriting");
+                return false;
+            }
+        }
+        return true;
+    }
+
     @BinderThread
     @Override
     public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
@@ -3373,10 +3471,7 @@
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
-            // TODO(b/261565259): to avoid using null, add package name in ClientState
-            final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
-            final int uid = mCurClient != null ? mCurClient.mUid : -1;
-            statsToken = ImeTracker.forLogging().onRequestShow(packageName, uid,
+            statsToken = createStatsTokenForFocusedClient(true /* show */,
                     ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
         }
 
@@ -3450,17 +3545,7 @@
             int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
-            // TODO(b/261565259): to avoid using null, add package name in ClientState
-            final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
-            final int uid;
-            if (mCurClient != null) {
-                uid = mCurClient.mUid;
-            } else if (mCurFocusedWindowClient != null) {
-                uid = mCurFocusedWindowClient.mUid;
-            } else {
-                uid = -1;
-            }
-            statsToken = ImeTracker.forLogging().onRequestHide(packageName, uid,
+            statsToken = createStatsTokenForFocusedClient(false /* show */,
                     ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
         }
 
@@ -3695,6 +3780,7 @@
         mCurFocusedWindow = windowToken;
         mCurFocusedWindowSoftInputMode = softInputMode;
         mCurFocusedWindowClient = cs;
+        mCurFocusedWindowEditorInfo = editorInfo;
         mCurPerceptible = true;
 
         // We want to start input before showing the IME, but after closing
@@ -4623,13 +4709,13 @@
                 mWindowManagerInternal.onToggleImeRequested(
                         show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
         mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
-                mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName,
+                mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName,
                 mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
                 info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
                 info.imeSurfaceParentName));
 
         if (statsToken != null) {
-            mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName);
+            mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
         }
     }
 
@@ -4852,6 +4938,13 @@
                 }
                 return true;
             }
+            case MSG_PREPARE_HANDWRITING_DELEGATION:
+                synchronized (ImfLock.class) {
+                    String delegate = (String) ((Pair) msg.obj).first;
+                    String delegator = (String) ((Pair) msg.obj).second;
+                    mHwController.prepareStylusHandwritingDelegation(delegate, delegator);
+                }
+                return true;
             case MSG_START_HANDWRITING:
                 synchronized (ImfLock.class) {
                     IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -5664,9 +5757,11 @@
             // We cannot simply distinguish a bad IME that reports an arbitrary package name from
             // an unfortunate IME whose internal state is already obsolete due to the asynchronous
             // nature of our system.  Let's compare it with our internal record.
-            if (!TextUtils.equals(mCurEditorInfo.packageName, packageName)) {
+            final var curPackageName = mCurEditorInfo != null
+                    ? mCurEditorInfo.packageName : null;
+            if (!TextUtils.equals(curPackageName, packageName)) {
                 Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
-                        + mCurEditorInfo.packageName + " packageName=" + packageName);
+                        + curPackageName + " packageName=" + packageName);
                 return null;
             }
             // This user ID can never bee spoofed.
@@ -6427,6 +6522,30 @@
         return mImeTrackerService;
     }
 
+    /**
+     * Creates an IME request tracking token for the current focused client.
+     *
+     * @param show whether this is a show or a hide request.
+     * @param origin the origin of the IME request.
+     * @param reason the reason why the IME request was created.
+     */
+    @NonNull
+    private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
+            @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+        final int uid = mCurFocusedWindowClient != null
+                ? mCurFocusedWindowClient.mUid
+                : -1;
+        final var packageName = mCurFocusedWindowEditorInfo != null
+                ? mCurFocusedWindowEditorInfo.packageName
+                : "uid(" + uid + ")";
+
+        if (show) {
+            return ImeTracker.forLogging().onRequestShow(packageName, uid, origin, reason);
+        } else {
+            return ImeTracker.forLogging().onRequestHide(packageName, uid, origin, reason);
+        }
+    }
+
     private static final class InputMethodPrivilegedOperationsImpl
             extends IInputMethodPrivilegedOperations.Stub {
         private final InputMethodManagerService mImms;
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 8f65775..94f12dd 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -370,11 +370,6 @@
             mLastRestartTimestampMap.put(contextHubId,
                     new AtomicLong(SystemClock.elapsedRealtimeNanos()));
 
-            IContextHubClient client = mClientManager.registerClient(
-                    contextHubInfo, createDefaultClientCallback(contextHubId),
-                    /* attributionTag= */ null, mTransactionManager, mContext.getPackageName());
-            defaultClientMap.put(contextHubId, client);
-
             try {
                 mContextHubWrapper.registerCallback(contextHubId,
                         new ContextHubServiceCallback(contextHubId));
@@ -383,6 +378,11 @@
                         + contextHubId + ")", e);
             }
 
+            IContextHubClient client = mClientManager.registerClient(
+                    contextHubInfo, createDefaultClientCallback(contextHubId),
+                    /* attributionTag= */ null, mTransactionManager, mContext.getPackageName());
+            defaultClientMap.put(contextHubId, client);
+
             // Do a query to initialize the service cache list of nanoapps
             // TODO(b/194289715): Remove this when old API is deprecated
             queryNanoAppsInternal(contextHubId);
@@ -1207,7 +1207,7 @@
         pw.println("");
         pw.println("=================== NANOAPPS ====================");
         // Dump nanoAppHash
-        mNanoAppStateManager.foreachNanoAppInstanceInfo((info) -> pw.println(info));
+        mNanoAppStateManager.foreachNanoAppInstanceInfo(pw::println);
 
         pw.println("");
         pw.println("=================== PRELOADED NANOAPPS ====================");
@@ -1255,16 +1255,17 @@
         proto.flush();
     }
 
-    /**
-     * Dumps preloaded nanoapps to the console
-     */
+    /** Dumps preloaded nanoapps to the console */
     private void dumpPreloadedNanoapps(PrintWriter pw) {
         if (mContextHubWrapper == null) {
             return;
         }
 
         long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds();
-        for (long preloadedNanoappId: preloadedNanoappIds) {
+        if (preloadedNanoappIds == null) {
+            return;
+        }
+        for (long preloadedNanoappId : preloadedNanoappIds) {
             pw.print("ID: 0x");
             pw.println(Long.toHexString(preloadedNanoappId));
         }
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index dee26e38..e592a22 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -1245,8 +1245,13 @@
                 }
                 sid = sidFromPasswordHandle(pwd.passwordHandle);
             }
-            protectorSecret = transformUnderSecdiscardable(stretchedLskf,
-                    loadSecdiscardable(protectorId, userId));
+            byte[] secdiscardable = loadSecdiscardable(protectorId, userId);
+            if (secdiscardable == null) {
+                Slog.e(TAG, "secdiscardable file not found");
+                result.gkResponse = VerifyCredentialResponse.ERROR;
+                return result;
+            }
+            protectorSecret = transformUnderSecdiscardable(stretchedLskf, secdiscardable);
         }
         // Supplied credential passes first stage weaver/gatekeeper check so it should be correct.
         // Notify the callback so the keyguard UI can proceed immediately.
@@ -1311,6 +1316,11 @@
             byte[] token, int userId) {
         AuthenticationResult result = new AuthenticationResult();
         byte[] secdiscardable = loadSecdiscardable(protectorId, userId);
+        if (secdiscardable == null) {
+            Slog.e(TAG, "secdiscardable file not found");
+            result.gkResponse = VerifyCredentialResponse.ERROR;
+            return result;
+        }
         int slotId = loadWeaverSlot(protectorId, userId);
         if (slotId != INVALID_WEAVER_SLOT) {
             if (!isWeaverAvailable()) {
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index c232b36..fac7748 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -678,4 +678,8 @@
 
     @NonNull
     Collection<SharedUserSetting> getAllSharedUsers();
+
+    boolean isChangeEnabled(long changeId, int uid);
+
+    boolean isChangeEnabled(long changeId, ApplicationInfo info);
 }
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 5984360..21f661b 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -125,6 +125,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.PackageDexUsage;
@@ -574,8 +575,7 @@
                     list = new ArrayList<>(1);
                     list.add(ri);
                     PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
-                            mInjector.getCompatibility(), mComponentResolver,
-                            list, false, intent, resolvedType, filterCallingUid);
+                            this, list, false, intent, resolvedType, filterCallingUid);
                 }
             }
         } else {
@@ -591,7 +591,7 @@
                     String callingPkgName = getInstantAppPackageName(filterCallingUid);
                     boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
                     lockedResult.result = maybeAddInstantAppInstaller(
-                            lockedResult.result, intent, resolvedType, flags,
+                            lockedResult.result, intent, resolvedType, flags, filterCallingUid,
                             userId, resolveForStart, isRequesterInstantApp);
                 }
                 if (lockedResult.sortResult) {
@@ -604,8 +604,7 @@
         if (originalIntent != null) {
             // We also have to ensure all components match the original intent
             PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
-                    mInjector.getCompatibility(), mComponentResolver,
-                    list, false, originalIntent, resolvedType, filterCallingUid);
+                    this, list, false, originalIntent, resolvedType, filterCallingUid);
         }
 
         return skipPostResolution ? list : applyPostResolutionFilter(
@@ -687,8 +686,7 @@
                     list = new ArrayList<>(1);
                     list.add(ri);
                     PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
-                            mInjector.getCompatibility(), mComponentResolver,
-                            list, false, intent, resolvedType, callingUid);
+                            this, list, false, intent, resolvedType, callingUid);
                 }
             }
         } else {
@@ -699,8 +697,7 @@
         if (originalIntent != null) {
             // We also have to ensure all components match the original intent
             PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
-                    mInjector.getCompatibility(), mComponentResolver,
-                    list, false, originalIntent, resolvedType, callingUid);
+                    this, list, false, originalIntent, resolvedType, callingUid);
         }
 
         return list;
@@ -713,7 +710,7 @@
         String pkgName = intent.getPackage();
         if (pkgName == null) {
             final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(this, intent,
-                    resolvedType, flags, userId);
+                    resolvedType, flags, callingUid, userId);
             if (resolveInfos == null) {
                 return Collections.emptyList();
             }
@@ -723,7 +720,7 @@
         final AndroidPackage pkg = mPackages.get(pkgName);
         if (pkg != null) {
             final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(this, intent,
-                    resolvedType, flags, pkg.getServices(),
+                    resolvedType, flags, pkg.getServices(), callingUid,
                     userId);
             if (resolveInfos == null) {
                 return Collections.emptyList();
@@ -753,7 +750,7 @@
                  {@link PackageManager.SKIP_CURRENT_PROFILE} set.
                  */
                 result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
-                        intent, resolvedType, flags, userId), userId));
+                        intent, resolvedType, flags, filterCallingUid, userId), userId));
             }
             addInstant = isInstantAppResolutionAllowed(intent, result, userId,
                     false /*skipPackageCheck*/, flags);
@@ -777,7 +774,7 @@
                     || !shouldFilterApplication(setting, filterCallingUid, userId))) {
                 result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
                         intent, resolvedType, flags, setting.getAndroidPackage().getActivities(),
-                        userId), userId));
+                        filterCallingUid, userId), userId));
             }
             if (result == null || result.size() == 0) {
                 // the caller wants to resolve for a particular package; however, there
@@ -1108,7 +1105,7 @@
             return null;
         }
         List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(this, intent,
-                resolvedType, flags, parentUserId);
+                resolvedType, flags, Binder.getCallingUid(), parentUserId);
 
         if (resultTargetUser == null || resultTargetUser.isEmpty()) {
             return null;
@@ -1346,7 +1343,7 @@
 
     private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result,
             Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
-            int userId, boolean resolveForStart, boolean isRequesterInstantApp) {
+            int callingUid, int userId, boolean resolveForStart, boolean isRequesterInstantApp) {
         // first, check to see if we've got an instant app already installed
         final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0;
         ResolveInfo localInstantApp = null;
@@ -1359,6 +1356,7 @@
                             | PackageManager.GET_RESOLVED_FILTER
                             | PackageManager.MATCH_INSTANT
                             | PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY,
+                    callingUid,
                     userId);
             for (int i = instantApps.size() - 1; i >= 0; --i) {
                 final ResolveInfo info = instantApps.get(i);
@@ -3693,10 +3691,13 @@
                 ps, callingUid, component, TYPE_ACTIVITY, userId, true /* filterUninstall */)) {
             return false;
         }
+        final boolean callerBlocksNullAction = isChangeEnabled(
+                IntentFilter.BLOCK_NULL_ACTION_INTENTS, callingUid);
         for (int i=0; i< a.getIntents().size(); i++) {
             if (a.getIntents().get(i).getIntentFilter()
                     .match(intent.getAction(), resolvedType, intent.getScheme(),
-                            intent.getData(), intent.getCategories(), TAG) >= 0) {
+                            intent.getData(), intent.getCategories(), TAG,
+                            /*supportWildcards*/ false, callerBlocksNullAction, null, null) >= 0) {
                 return true;
             }
         }
@@ -5794,4 +5795,37 @@
     public UserInfo[] getUserInfos() {
         return mInjector.getUserManagerInternal().getUserInfos();
     }
+
+    @Override
+    public boolean isChangeEnabled(long changeId, int uid) {
+        final PlatformCompat compat = mInjector.getCompatibility();
+        int appId = UserHandle.getAppId(uid);
+        SettingBase s = mSettings.getSettingBase(appId);
+        if (s instanceof PackageSetting) {
+            var ps = (PackageSetting) s;
+            if (ps.getAndroidPackage() == null) return false;
+            var info = new ApplicationInfo();
+            info.packageName = ps.getPackageName();
+            info.targetSdkVersion = ps.getAndroidPackage().getTargetSdkVersion();
+            return compat.isChangeEnabledInternal(changeId, info);
+        } else if (s instanceof SharedUserSetting) {
+            var ss = (SharedUserSetting) s;
+            List<AndroidPackage> packages = ss.getPackages();
+            for (int i = 0; i < packages.size(); ++i) {
+                var pkg = packages.get(i);
+                var info = new ApplicationInfo();
+                info.packageName = pkg.getPackageName();
+                info.targetSdkVersion = pkg.getTargetSdkVersion();
+                if (compat.isChangeEnabledInternal(changeId, info)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isChangeEnabled(long changeId, ApplicationInfo info) {
+        return mInjector.getCompatibility().isChangeEnabledInternal(changeId, info);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java b/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java
index 90d89c6..ee8f560 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java
@@ -23,6 +23,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
+import android.os.Binder;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.app.IntentForwarderActivity;
@@ -278,7 +279,7 @@
         }
 
         List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(computer, intent,
-                resolvedType, flags, targetUserId);
+                resolvedType, flags, Binder.getCallingUid(), targetUserId);
         if (CollectionUtils.isEmpty(resultTargetUser)) {
             return null;
         }
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 1bd5b99..0fd81ac 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -40,6 +40,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppGlobals;
+import android.app.role.RoleManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -272,7 +273,6 @@
      * compiles it if needed.
      */
     private void checkAndDexOptSystemUi(int reason) throws LegacyDexoptDisabledException {
-        Installer.checkLegacyDexoptDisabled();
         Computer snapshot = mPm.snapshotComputer();
         String sysUiPackageName =
                 mPm.mContext.getString(com.android.internal.R.string.config_systemUi);
@@ -288,7 +288,7 @@
         String compilerFilter;
 
         if (isProfileGuidedCompilerFilter(targetCompilerFilter)) {
-            compilerFilter = defaultCompilerFilter;
+            compilerFilter = "verify";
             File profileFile = new File(getPrebuildProfilePath(pkg));
 
             // Copy the profile to the reference profile path if it exists. Installd can only use a
@@ -312,7 +312,26 @@
             compilerFilter = targetCompilerFilter;
         }
 
-        // We don't expect updates in current profiles to be significant here, but
+        performDexoptPackage(sysUiPackageName, reason, compilerFilter);
+    }
+
+    private void dexoptLauncher(int reason) throws LegacyDexoptDisabledException {
+        Computer snapshot = mPm.snapshotComputer();
+        RoleManager roleManager = mPm.mContext.getSystemService(RoleManager.class);
+        for (var packageName : roleManager.getRoleHolders(RoleManager.ROLE_HOME)) {
+            AndroidPackage pkg = snapshot.getPackage(packageName);
+            if (pkg == null) {
+                Log.w(TAG, "Launcher package " + packageName + " is not found for dexopting");
+            } else {
+                performDexoptPackage(packageName, reason, "speed-profile");
+            }
+        }
+    }
+
+    private void performDexoptPackage(@NonNull String packageName, int reason,
+            @NonNull String compilerFilter) throws LegacyDexoptDisabledException {
+        Installer.checkLegacyDexoptDisabled();
+
         // DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
         // unconditionally enabled for profile guided filters when ART Service is called instead of
         // the legacy PackageDexOptimizer implementation.
@@ -320,8 +339,8 @@
                 ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
                 : 0;
 
-        performDexOptTraced(new DexoptOptions(pkg.getPackageName(), REASON_BOOT_AFTER_OTA,
-                compilerFilter, null /* splitName */, dexoptFlags));
+        performDexOptTraced(new DexoptOptions(
+                packageName, reason, compilerFilter, null /* splitName */, dexoptFlags));
     }
 
     /**
@@ -343,6 +362,10 @@
             return;
         }
 
+        Log.i(TAG,
+                "Starting boot dexopt for reason "
+                        + DexoptOptions.convertToArtServiceDexoptReason(reason));
+
         final long startTime = System.nanoTime();
 
         if (useArtService()) {
@@ -351,9 +374,10 @@
                     null /* progressCallbackExecutor */, null /* progressCallback */);
         } else {
             try {
-                // System UI is important to user experience, so we check it after a mainline update
-                // or an OTA. It may need to be re-compiled in these cases.
+                // System UI and the launcher are important to user experience, so we check them
+                // after a mainline update or OTA. They may need to be re-compiled in these cases.
                 checkAndDexOptSystemUi(reason);
+                dexoptLauncher(reason);
 
                 if (reason != REASON_BOOT_AFTER_OTA && reason != REASON_FIRST_BOOT) {
                     return;
diff --git a/services/core/java/com/android/server/pm/NoFilteringResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java
index 3923890..e53e756 100644
--- a/services/core/java/com/android/server/pm/NoFilteringResolver.java
+++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java
@@ -102,7 +102,7 @@
             boolean hasNonNegativePriorityResult,
             Function<String, PackageStateInternal> pkgSettingFunction) {
         List<ResolveInfo> resolveInfos = mComponentResolver.queryActivities(computer,
-                intent, resolvedType, flags, targetUserId);
+                intent, resolvedType, flags, Binder.getCallingUid(), targetUserId);
         List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
         if (resolveInfos != null) {
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 928ffa7..9036d4c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.IntentFilter.BLOCK_NULL_ACTION_INTENTS;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
@@ -94,7 +95,6 @@
 import com.android.server.IntentResolver;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.Watchdog;
-import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
@@ -1166,10 +1166,8 @@
         return (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
     }
 
-    // Static to give access to ComputeEngine
     public static void applyEnforceIntentFilterMatching(
-            PlatformCompat compat, ComponentResolverApi resolver,
-            List<ResolveInfo> resolveInfos, boolean isReceiver,
+            Computer computer, List<ResolveInfo> resolveInfos, boolean isReceiver,
             Intent intent, String resolvedType, int filterCallingUid) {
         if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
 
@@ -1177,6 +1175,11 @@
                 ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
                 : null;
 
+        final boolean callerBlocksNullAction = computer.isChangeEnabled(
+                BLOCK_NULL_ACTION_INTENTS, filterCallingUid);
+
+        final ComponentResolverApi resolver = computer.getComponentResolver();
+
         for (int i = resolveInfos.size() - 1; i >= 0; --i) {
             final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
 
@@ -1187,11 +1190,15 @@
             }
 
             // Only enforce filter matching if target app's target SDK >= T
-            if (!compat.isChangeEnabledInternal(
+            if (!computer.isChangeEnabled(
                     ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo)) {
                 continue;
             }
 
+            // Block null action intent if either source or target app's target SDK >= U
+            final boolean blockNullAction = callerBlocksNullAction
+                    || computer.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS, info.applicationInfo);
+
             final ParsedMainComponent comp;
             if (info instanceof ActivityInfo) {
                 if (isReceiver) {
@@ -1213,7 +1220,8 @@
             boolean match = false;
             for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
                 IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
-                if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) {
+                if (IntentResolver.intentMatchesFilter(
+                        intentFilter, intent, resolvedType, blockNullAction)) {
                     match = true;
                     break;
                 }
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index a13c568..8bca4a9 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -466,15 +466,14 @@
                     list = new ArrayList<>(1);
                     list.add(ri);
                     PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
-                            mPlatformCompat, componentResolver, list, true, intent,
-                            resolvedType, filterCallingUid);
+                            computer, list, true, intent, resolvedType, filterCallingUid);
                 }
             }
         } else {
             String pkgName = intent.getPackage();
             if (pkgName == null) {
-                final List<ResolveInfo> result = componentResolver
-                        .queryReceivers(computer, intent, resolvedType, flags, userId);
+                final List<ResolveInfo> result = componentResolver.queryReceivers(
+                        computer, intent, resolvedType, flags, filterCallingUid, userId);
                 if (result != null) {
                     list = result;
                 }
@@ -482,7 +481,7 @@
             final AndroidPackage pkg = computer.getPackage(pkgName);
             if (pkg != null) {
                 final List<ResolveInfo> result = componentResolver.queryReceivers(computer,
-                        intent, resolvedType, flags, pkg.getReceivers(), userId);
+                        intent, resolvedType, flags, pkg.getReceivers(), filterCallingUid, userId);
                 if (result != null) {
                     list = result;
                 }
@@ -492,8 +491,7 @@
         if (originalIntent != null) {
             // We also have to ensure all components match the original intent
             PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
-                    mPlatformCompat, componentResolver,
-                    list, true, originalIntent, resolvedType, filterCallingUid);
+                    computer, list, true, originalIntent, resolvedType, filterCallingUid);
         }
 
         return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
@@ -577,7 +575,7 @@
         String pkgName = intent.getPackage();
         if (pkgName == null) {
             final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer,
-                    intent, resolvedType, flags, userId);
+                    intent, resolvedType, flags, callingUid, userId);
             if (resolveInfos == null) {
                 return Collections.emptyList();
             }
@@ -587,7 +585,7 @@
         final AndroidPackage pkg = computer.getPackage(pkgName);
         if (pkg != null) {
             final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer,
-                    intent, resolvedType, flags, pkg.getProviders(), userId);
+                    intent, resolvedType, flags, pkg.getProviders(), callingUid, userId);
             if (resolveInfos == null) {
                 return Collections.emptyList();
             }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 1cd9ec6..c6411ff 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -4323,8 +4323,14 @@
             @NonNull ComponentName activity, @UserIdInt int userId) {
         final long start = getStatStartTime();
         try {
-            return queryActivities(new Intent(), activity.getPackageName(), activity, userId)
-                    .size() > 0;
+            final ActivityInfo ai;
+            try {
+                ai = mContext.getPackageManager().getActivityInfoAsUser(activity,
+                        PackageManager.ComponentInfoFlags.of(PACKAGE_MATCH_FLAGS), userId);
+            } catch (NameNotFoundException e) {
+                return false;
+            }
+            return ai.enabled && ai.exported;
         } finally {
             logDurationStat(Stats.IS_ACTIVITY_ENABLED, start);
         }
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index fac681a..977fab1 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -807,10 +807,10 @@
         }
     }
 
-    private abstract static class MimeGroupsAwareIntentResolver<F extends Pair<?
-            extends ParsedComponent, ParsedIntentInfo>, R>
-            extends IntentResolver<F, R> {
-        private final ArrayMap<String, F[]> mMimeGroupToFilter = new ArrayMap<>();
+    private abstract static class MimeGroupsAwareIntentResolver<F extends ParsedComponent>
+            extends IntentResolver<Pair<F, ParsedIntentInfo>, ResolveInfo> {
+        private final ArrayMap<String, Pair<F, ParsedIntentInfo>[]> mMimeGroupToFilter =
+                new ArrayMap<>();
         private boolean mIsUpdatingMimeGroup = false;
 
         @NonNull
@@ -822,7 +822,7 @@
         }
 
         // Copy constructor used in creating snapshots
-        MimeGroupsAwareIntentResolver(MimeGroupsAwareIntentResolver<F, R> orig,
+        MimeGroupsAwareIntentResolver(MimeGroupsAwareIntentResolver<F> orig,
                 @NonNull UserManagerService userManager) {
             mUserManager = userManager;
             copyFrom(orig);
@@ -831,7 +831,7 @@
         }
 
         @Override
-        public void addFilter(@Nullable PackageDataSnapshot snapshot, F f) {
+        public void addFilter(@Nullable PackageDataSnapshot snapshot, Pair<F, ParsedIntentInfo> f) {
             IntentFilter intentFilter = getIntentFilter(f);
             // We assume Computer is available for this class and all subclasses. Because this class
             // uses subclass method override to handle logic, the Computer parameter must be in the
@@ -846,7 +846,7 @@
         }
 
         @Override
-        protected void removeFilterInternal(F f) {
+        protected void removeFilterInternal(Pair<F, ParsedIntentInfo> f) {
             IntentFilter intentFilter = getIntentFilter(f);
             if (!mIsUpdatingMimeGroup) {
                 unregister_intent_filter(f, intentFilter.mimeGroupsIterator(), mMimeGroupToFilter,
@@ -857,6 +857,86 @@
             intentFilter.clearDynamicDataTypes();
         }
 
+        @Override
+        public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+                String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId) {
+            if (!mUserManager.exists(userId)) return null;
+            long flags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0);
+            return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, callingUid,
+                    userId, flags);
+        }
+
+        List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
+                String resolvedType, long flags, int callingUid, int userId) {
+            if (!mUserManager.exists(userId)) return null;
+            return super.queryIntent(computer, intent, resolvedType,
+                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, callingUid, userId, flags);
+        }
+
+        List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
+                String resolvedType, long flags, List<F> packageComponents,
+                int callingUid, int userId) {
+            if (!mUserManager.exists(userId)) {
+                return null;
+            }
+            if (packageComponents == null) {
+                return Collections.emptyList();
+            }
+            final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
+            final int componentsSize = packageComponents.size();
+            ArrayList<Pair<F, ParsedIntentInfo>[]> listCut = new ArrayList<>(componentsSize);
+
+            List<ParsedIntentInfo> intentFilters;
+            for (int i = 0; i < componentsSize; ++i) {
+                F component = packageComponents.get(i);
+                intentFilters = component.getIntents();
+                if (!intentFilters.isEmpty()) {
+                    Pair<F, ParsedIntentInfo>[] array = newArray(intentFilters.size());
+                    for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
+                        array[arrayIndex] = Pair.create(component, intentFilters.get(arrayIndex));
+                    }
+                    listCut.add(array);
+                }
+            }
+            return super.queryIntentFromList(computer, intent, resolvedType,
+                    defaultOnly, listCut, callingUid, userId, flags);
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName, Pair<F, ParsedIntentInfo> info) {
+            return packageName.equals(info.first.getPackageName());
+        }
+
+        @Override
+        protected void sortResults(List<ResolveInfo> results) {
+            results.sort(RESOLVE_PRIORITY_SORTER);
+        }
+
+        @Override
+        protected void filterResults(@NonNull Computer computer, @NonNull Intent intent,
+                List<ResolveInfo> results) {
+            if (intent.getAction() != null) return;
+            // When the resolved component is targeting U+, block null action intents
+            for (int i = results.size() - 1; i >= 0; --i) {
+                if (computer.isChangeEnabled(IntentFilter.BLOCK_NULL_ACTION_INTENTS,
+                        results.get(i).getComponentInfo().applicationInfo)) {
+                    results.remove(i);
+                }
+            }
+        }
+
+        @Override
+        protected Pair<F, ParsedIntentInfo>[] newArray(int size) {
+            //noinspection unchecked
+            return (Pair<F, ParsedIntentInfo>[]) new Pair<?, ?>[size];
+        }
+
+        @Override
+        protected IntentFilter getIntentFilter(
+                @NonNull Pair<F, ParsedIntentInfo> input) {
+            return input.second.getIntentFilter();
+        }
+
         /**
          * Updates MIME group by applying changes to all IntentFilters
          * that contain the group and repopulating m*ToFilter maps accordingly
@@ -867,12 +947,12 @@
          */
         public boolean updateMimeGroup(@NonNull Computer computer, String packageName,
                 String mimeGroup) {
-            F[] filters = mMimeGroupToFilter.get(mimeGroup);
+            Pair<F, ParsedIntentInfo>[] filters = mMimeGroupToFilter.get(mimeGroup);
             int n = filters != null ? filters.length : 0;
 
             mIsUpdatingMimeGroup = true;
             boolean hasChanges = false;
-            F filter;
+            Pair<F, ParsedIntentInfo> filter;
             for (int i = 0; i < n && (filter = filters[i]) != null; i++) {
                 if (isPackageForFilter(packageName, filter)) {
                     hasChanges |= updateFilter(computer, filter);
@@ -882,7 +962,7 @@
             return hasChanges;
         }
 
-        private boolean updateFilter(@NonNull Computer computer, F f) {
+        private boolean updateFilter(@NonNull Computer computer, Pair<F, ParsedIntentInfo> f) {
             IntentFilter filter = getIntentFilter(f);
             List<String> oldTypes = filter.dataTypes();
             removeFilter(f);
@@ -907,7 +987,7 @@
             return first.equals(second);
         }
 
-        private void applyMimeGroups(@NonNull Computer computer, F f) {
+        private void applyMimeGroups(@NonNull Computer computer, Pair<F, ParsedIntentInfo> f) {
             IntentFilter filter = getIntentFilter(f);
 
             for (int i = filter.countMimeGroups() - 1; i >= 0; i--) {
@@ -931,8 +1011,8 @@
         }
 
         @Override
-        protected boolean isFilterStopped(@NonNull Computer computer, F filter,
-                @UserIdInt int userId) {
+        protected boolean isFilterStopped(@NonNull Computer computer,
+                Pair<F, ParsedIntentInfo> filter, @UserIdInt int userId) {
             if (!mUserManager.exists(userId)) {
                 return true;
             }
@@ -948,7 +1028,7 @@
     }
 
     public static class ActivityIntentResolver
-            extends MimeGroupsAwareIntentResolver<Pair<ParsedActivity, ParsedIntentInfo>, ResolveInfo> {
+            extends MimeGroupsAwareIntentResolver<ParsedActivity> {
 
         @NonNull
         private UserNeedsBadgingCache mUserNeedsBadging;
@@ -969,53 +1049,6 @@
             mUserNeedsBadging = userNeedsBadgingCache;
         }
 
-        @Override
-        public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
-                String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
-            if (!mUserManager.exists(userId)) return null;
-            long flags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0);
-            return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
-        }
-
-        List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
-                String resolvedType, long flags, int userId) {
-            if (!mUserManager.exists(userId)) {
-                return null;
-            }
-            return super.queryIntent(computer, intent, resolvedType,
-                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
-        }
-
-        List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
-                String resolvedType, long flags, List<ParsedActivity> packageActivities,
-                int userId) {
-            if (!mUserManager.exists(userId)) {
-                return null;
-            }
-            if (packageActivities == null) {
-                return Collections.emptyList();
-            }
-            final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
-            final int activitiesSize = packageActivities.size();
-            ArrayList<Pair<ParsedActivity, ParsedIntentInfo>[]> listCut =
-                    new ArrayList<>(activitiesSize);
-
-            List<ParsedIntentInfo> intentFilters;
-            for (int i = 0; i < activitiesSize; ++i) {
-                ParsedActivity activity = packageActivities.get(i);
-                intentFilters = activity.getIntents();
-                if (!intentFilters.isEmpty()) {
-                    Pair<ParsedActivity, ParsedIntentInfo>[] array = newArray(intentFilters.size());
-                    for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
-                        array[arrayIndex] = Pair.create(activity, intentFilters.get(arrayIndex));
-                    }
-                    listCut.add(array);
-                }
-            }
-            return super.queryIntentFromList(computer, intent, resolvedType,
-                    defaultOnly, listCut, userId, flags);
-        }
-
         protected void addActivity(@NonNull Computer computer, ParsedActivity a, String type,
                 List<Pair<ParsedActivity, ParsedIntentInfo>> newIntents) {
             mActivities.put(a.getComponentName(), a);
@@ -1072,18 +1105,6 @@
             return true;
         }
 
-        @Override
-        protected Pair<ParsedActivity, ParsedIntentInfo>[] newArray(int size) {
-            //noinspection unchecked
-            return (Pair<ParsedActivity, ParsedIntentInfo>[]) new Pair<?, ?>[size];
-        }
-
-        @Override
-        protected boolean isPackageForFilter(String packageName,
-                Pair<ParsedActivity, ParsedIntentInfo> info) {
-            return packageName.equals(info.first.getPackageName());
-        }
-
         private void log(String reason, ParsedIntentInfo info, int match,
                 int userId) {
             Slog.w(TAG, reason
@@ -1199,11 +1220,6 @@
         }
 
         @Override
-        protected void sortResults(List<ResolveInfo> results) {
-            results.sort(RESOLVE_PRIORITY_SORTER);
-        }
-
-        @Override
         protected void dumpFilter(PrintWriter out, String prefix,
                 Pair<ParsedActivity, ParsedIntentInfo> pair) {
             ParsedActivity activity = pair.first;
@@ -1237,12 +1253,6 @@
             out.println();
         }
 
-        @Override
-        protected IntentFilter getIntentFilter(
-                @NonNull Pair<ParsedActivity, ParsedIntentInfo> input) {
-            return input.second.getIntentFilter();
-        }
-
         protected List<ParsedActivity> getResolveList(AndroidPackage pkg) {
             return pkg.getActivities();
         }
@@ -1278,7 +1288,7 @@
     }
 
     public static final class ProviderIntentResolver
-            extends MimeGroupsAwareIntentResolver<Pair<ParsedProvider, ParsedIntentInfo>, ResolveInfo> {
+            extends MimeGroupsAwareIntentResolver<ParsedProvider> {
         // Default constructor
         ProviderIntentResolver(@NonNull UserManagerService userManager) {
             super(userManager);
@@ -1291,57 +1301,6 @@
             mProviders.putAll(orig.mProviders);
         }
 
-        @Override
-        public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
-                String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
-            if (!mUserManager.exists(userId)) {
-                return null;
-            }
-            long flags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
-            return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
-        }
-
-        @Nullable
-        List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
-                String resolvedType, long flags, int userId) {
-            if (!mUserManager.exists(userId)) {
-                return null;
-            }
-            return super.queryIntent(computer, intent, resolvedType,
-                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
-        }
-
-        @Nullable
-        List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
-                String resolvedType, long flags, List<ParsedProvider> packageProviders,
-                int userId) {
-            if (!mUserManager.exists(userId)) {
-                return null;
-            }
-            if (packageProviders == null) {
-                return Collections.emptyList();
-            }
-            final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
-            final int providersSize = packageProviders.size();
-            ArrayList<Pair<ParsedProvider, ParsedIntentInfo>[]> listCut =
-                    new ArrayList<>(providersSize);
-
-            List<ParsedIntentInfo> intentFilters;
-            for (int i = 0; i < providersSize; ++i) {
-                ParsedProvider provider = packageProviders.get(i);
-                intentFilters = provider.getIntents();
-                if (!intentFilters.isEmpty()) {
-                    Pair<ParsedProvider, ParsedIntentInfo>[] array = newArray(intentFilters.size());
-                    for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
-                        array[arrayIndex] = Pair.create(provider, intentFilters.get(arrayIndex));
-                    }
-                    listCut.add(array);
-                }
-            }
-            return super.queryIntentFromList(computer, intent, resolvedType,
-                    defaultOnly, listCut, userId, flags);
-        }
-
         void addProvider(@NonNull Computer computer, ParsedProvider p) {
             if (mProviders.containsKey(p.getComponentName())) {
                 Slog.w(TAG, "Provider " + p.getComponentName() + " already defined; ignoring");
@@ -1402,18 +1361,6 @@
         }
 
         @Override
-        protected Pair<ParsedProvider, ParsedIntentInfo>[] newArray(int size) {
-            //noinspection unchecked
-            return (Pair<ParsedProvider, ParsedIntentInfo>[]) new Pair<?, ?>[size];
-        }
-
-        @Override
-        protected boolean isPackageForFilter(String packageName,
-                Pair<ParsedProvider, ParsedIntentInfo> info) {
-            return packageName.equals(info.first.getPackageName());
-        }
-
-        @Override
         protected ResolveInfo newResult(@NonNull Computer computer,
                 Pair<ParsedProvider, ParsedIntentInfo> pair, int match, int userId,
                 long customFlags) {
@@ -1479,11 +1426,6 @@
         }
 
         @Override
-        protected void sortResults(List<ResolveInfo> results) {
-            results.sort(RESOLVE_PRIORITY_SORTER);
-        }
-
-        @Override
         protected void dumpFilter(PrintWriter out, String prefix,
                 Pair<ParsedProvider, ParsedIntentInfo> pair) {
             ParsedProvider provider = pair.first;
@@ -1518,17 +1460,11 @@
             out.println();
         }
 
-        @Override
-        protected IntentFilter getIntentFilter(
-                @NonNull Pair<ParsedProvider, ParsedIntentInfo> input) {
-            return input.second.getIntentFilter();
-        }
-
         final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>();
     }
 
     public static final class ServiceIntentResolver
-            extends MimeGroupsAwareIntentResolver<Pair<ParsedService, ParsedIntentInfo>, ResolveInfo> {
+            extends MimeGroupsAwareIntentResolver<ParsedService> {
         // Default constructor
         ServiceIntentResolver(@NonNull UserManagerService userManager) {
             super(userManager);
@@ -1541,50 +1477,6 @@
             mServices.putAll(orig.mServices);
         }
 
-        @Override
-        public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
-                String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
-            if (!mUserManager.exists(userId)) {
-                return null;
-            }
-            long flags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
-            return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
-        }
-
-        List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
-                String resolvedType, long flags, int userId) {
-            if (!mUserManager.exists(userId)) return null;
-            return super.queryIntent(computer, intent, resolvedType,
-                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
-        }
-
-        List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
-                String resolvedType, long flags, List<ParsedService> packageServices, int userId) {
-            if (!mUserManager.exists(userId)) return null;
-            if (packageServices == null) {
-                return Collections.emptyList();
-            }
-            final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
-            final int servicesSize = packageServices.size();
-            ArrayList<Pair<ParsedService, ParsedIntentInfo>[]> listCut =
-                    new ArrayList<>(servicesSize);
-
-            List<ParsedIntentInfo> intentFilters;
-            for (int i = 0; i < servicesSize; ++i) {
-                ParsedService service = packageServices.get(i);
-                intentFilters = service.getIntents();
-                if (intentFilters.size() > 0) {
-                    Pair<ParsedService, ParsedIntentInfo>[] array = newArray(intentFilters.size());
-                    for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
-                        array[arrayIndex] = Pair.create(service, intentFilters.get(arrayIndex));
-                    }
-                    listCut.add(array);
-                }
-            }
-            return super.queryIntentFromList(computer, intent, resolvedType,
-                    defaultOnly, listCut, userId, flags);
-        }
-
         void addService(@NonNull Computer computer, ParsedService s) {
             mServices.put(s.getComponentName(), s);
             if (DEBUG_SHOW_INFO) {
@@ -1640,18 +1532,6 @@
         }
 
         @Override
-        protected Pair<ParsedService, ParsedIntentInfo>[] newArray(int size) {
-            //noinspection unchecked
-            return (Pair<ParsedService, ParsedIntentInfo>[]) new Pair<?, ?>[size];
-        }
-
-        @Override
-        protected boolean isPackageForFilter(String packageName,
-                Pair<ParsedService, ParsedIntentInfo> info) {
-            return packageName.equals(info.first.getPackageName());
-        }
-
-        @Override
         protected ResolveInfo newResult(@NonNull Computer computer,
                 Pair<ParsedService, ParsedIntentInfo> pair, int match, int userId,
                 long customFlags) {
@@ -1710,11 +1590,6 @@
         }
 
         @Override
-        protected void sortResults(List<ResolveInfo> results) {
-            results.sort(RESOLVE_PRIORITY_SORTER);
-        }
-
-        @Override
         protected void dumpFilter(PrintWriter out, String prefix,
                 Pair<ParsedService, ParsedIntentInfo> pair) {
             ParsedService service = pair.first;
@@ -1752,12 +1627,6 @@
             out.println();
         }
 
-        @Override
-        protected IntentFilter getIntentFilter(
-                @NonNull Pair<ParsedService, ParsedIntentInfo> input) {
-            return input.second.getIntentFilter();
-        }
-
         // Keys are String (activity class name), values are Activity.
         final ArrayMap<ComponentName, ParsedService> mServices = new ArrayMap<>();
     }
@@ -1821,7 +1690,8 @@
         }
 
         @Override
-        protected void filterResults(List<AuxiliaryResolveInfo.AuxiliaryFilter> results) {
+        protected void filterResults(@NonNull Computer computer,
+                @NonNull Intent intent, List<AuxiliaryResolveInfo.AuxiliaryFilter> results) {
             // only do work if ordering is enabled [most of the time it won't be]
             if (mOrderResult.size() == 0) {
                 return;
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
index b8e4c8d..7f88660 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
@@ -55,12 +55,12 @@
 
     @Nullable
     List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, @UserIdInt int userId);
+            @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
 
     @Nullable
     List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
-            @UserIdInt int userId);
+            int callingUid, @UserIdInt int userId);
 
     @Nullable
     ProviderInfo queryProvider(@NonNull Computer computer, @NonNull String authority, long flags,
@@ -68,12 +68,12 @@
 
     @Nullable
     List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, @UserIdInt int userId);
+            @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
 
     @Nullable
     List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
-            @UserIdInt int userId);
+            int callingUid, @UserIdInt int userId);
 
     @Nullable
     List<ProviderInfo> queryProviders(@NonNull Computer computer, @Nullable String processName,
@@ -81,21 +81,21 @@
 
     @Nullable
     List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, @UserIdInt int userId);
+            @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
 
     @Nullable
     List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
-            @UserIdInt int userId);
+            int callingUid, @UserIdInt int userId);
 
     @Nullable
     List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, @UserIdInt int userId);
+            @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
 
     @Nullable
     List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
-            @UserIdInt int userId);
+            int callingUid, @UserIdInt int userId);
 
     void querySyncProviders(@NonNull Computer computer, @NonNull List<String> outNames,
             @NonNull List<ProviderInfo> outInfo, boolean safeMode, @UserIdInt int userId);
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
index 9115775..6899924 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
@@ -126,17 +126,17 @@
     @Nullable
     @Override
     public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, int userId) {
-        return mActivities.queryIntent(computer, intent, resolvedType, flags, userId);
+            @Nullable String resolvedType, long flags, int callingUid, int userId) {
+        return mActivities.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
     }
 
     @Nullable
     @Override
     public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
-            int userId) {
+            int callingUid, int userId) {
         return mActivities.queryIntentForPackage(computer, intent, resolvedType, flags, activities,
-                userId);
+                callingUid, userId);
     }
 
     @Nullable
@@ -168,17 +168,17 @@
     @Nullable
     @Override
     public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, int userId) {
-        return mProviders.queryIntent(computer, intent, resolvedType, flags, userId);
+            @Nullable String resolvedType, long flags, int callingUid, int userId) {
+        return mProviders.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
     }
 
     @Nullable
     @Override
     public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
-            @UserIdInt int userId) {
+            int callingUid, @UserIdInt int userId) {
         return mProviders.queryIntentForPackage(computer, intent, resolvedType, flags, providers,
-                userId);
+                callingUid, userId);
     }
 
     @Nullable
@@ -241,33 +241,33 @@
     @Nullable
     @Override
     public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, int userId) {
-        return mReceivers.queryIntent(computer, intent, resolvedType, flags, userId);
+            @Nullable String resolvedType, long flags, int callingUid, int userId) {
+        return mReceivers.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
     }
 
     @Nullable
     @Override
     public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
-            @UserIdInt int userId) {
+            int callingUid, @UserIdInt int userId) {
         return mReceivers.queryIntentForPackage(computer, intent, resolvedType, flags, receivers,
-                userId);
+                callingUid, userId);
     }
 
     @Nullable
     @Override
     public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, @UserIdInt int userId) {
-        return mServices.queryIntent(computer, intent, resolvedType, flags, userId);
+            @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
+        return mServices.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
     }
 
     @Nullable
     @Override
     public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
-            @UserIdInt int userId) {
+            int callingUid, @UserIdInt int userId) {
         return mServices.queryIntentForPackage(computer, intent, resolvedType, flags, services,
-                userId);
+                callingUid, userId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
index 0c84f4c..5bfb135 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
@@ -92,9 +92,9 @@
     @Nullable
     @Override
     public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+            @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
         synchronized (mLock) {
-            return super.queryActivities(computer, intent, resolvedType, flags, userId);
+            return super.queryActivities(computer, intent, resolvedType, flags, callingUid, userId);
         }
     }
 
@@ -102,9 +102,10 @@
     @Override
     public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
-            @UserIdInt int userId) {
+            int callingUid, @UserIdInt int userId) {
         synchronized (mLock) {
-            return super.queryActivities(computer, intent, resolvedType, flags, activities, userId);
+            return super.queryActivities(computer, intent, resolvedType, flags, activities,
+                    callingUid, userId);
         }
     }
 
@@ -120,9 +121,9 @@
     @Nullable
     @Override
     public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+            @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
         synchronized (mLock) {
-            return super.queryProviders(computer, intent, resolvedType, flags, userId);
+            return super.queryProviders(computer, intent, resolvedType, flags, callingUid, userId);
         }
     }
 
@@ -130,9 +131,10 @@
     @Override
     public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
-            @UserIdInt int userId) {
+            int callingUid, @UserIdInt int userId) {
         synchronized (mLock) {
-            return super.queryProviders(computer, intent, resolvedType, flags, providers, userId);
+            return super.queryProviders(computer, intent, resolvedType, flags, providers,
+                    callingUid, userId);
         }
     }
 
@@ -149,9 +151,9 @@
     @Nullable
     @Override
     public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+            @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
         synchronized (mLock) {
-            return super.queryReceivers(computer, intent, resolvedType, flags, userId);
+            return super.queryReceivers(computer, intent, resolvedType, flags, callingUid, userId);
         }
     }
 
@@ -159,18 +161,19 @@
     @Override
     public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
-            @UserIdInt int userId) {
+            int callingUid, @UserIdInt int userId) {
         synchronized (mLock) {
-            return super.queryReceivers(computer, intent, resolvedType, flags, receivers, userId);
+            return super.queryReceivers(computer, intent, resolvedType, flags, receivers,
+                    callingUid, userId);
         }
     }
 
     @Nullable
     @Override
     public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
-            @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+            @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
         synchronized (mLock) {
-            return super.queryServices(computer, intent, resolvedType, flags, userId);
+            return super.queryServices(computer, intent, resolvedType, flags, callingUid, userId);
         }
     }
 
@@ -178,9 +181,10 @@
     @Override
     public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
             @Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
-            @UserIdInt int userId) {
+            int callingUid, @UserIdInt int userId) {
         synchronized (mLock) {
-            return super.queryServices(computer, intent, resolvedType, flags, services, userId);
+            return super.queryServices(computer, intent, resolvedType, flags, services, callingUid,
+                    userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index db44e14..0e99e7e 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -194,27 +194,38 @@
 
         mPackageManagerInternal.getPackageList(new PackageListObserver() {
             @Override
-            public void onPackageAdded(String packageName, int uid) {
-                final int userId = UserHandle.getUserId(uid);
-                if (isStarted(userId)) {
-                    synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
+            public void onPackageAdded(String packageName, int appId) {
+                final int[] userIds = LocalServices.getService(UserManagerInternal.class)
+                        .getUserIds();
+                for (final int userId : userIds) {
+                    if (isStarted(userId)) {
+                        synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
+                    }
                 }
             }
 
             @Override
-            public void onPackageChanged(String packageName, int uid) {
-                final int userId = UserHandle.getUserId(uid);
-                if (isStarted(userId)) {
-                    synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
-                    resetAppOpPermissionsIfNotRequestedForUid(uid);
+            public void onPackageChanged(String packageName, int appId) {
+                final int[] userIds = LocalServices.getService(UserManagerInternal.class)
+                        .getUserIds();
+                for (final int userId : userIds) {
+                    if (isStarted(userId)) {
+                        synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
+                        final int uid = UserHandle.getUid(userId, appId);
+                        resetAppOpPermissionsIfNotRequestedForUid(uid);
+                    }
                 }
             }
 
             @Override
-            public void onPackageRemoved(String packageName, int uid) {
-                final int userId = UserHandle.getUserId(uid);
-                if (isStarted(userId)) {
-                    resetAppOpPermissionsIfNotRequestedForUid(uid);
+            public void onPackageRemoved(String packageName, int appId) {
+                final int[] userIds = LocalServices.getService(UserManagerInternal.class)
+                        .getUserIds();
+                for (final int userId : userIds) {
+                    if (isStarted(userId)) {
+                        final int uid = UserHandle.getUid(userId, appId);
+                        resetAppOpPermissionsIfNotRequestedForUid(uid);
+                    }
                 }
             }
         });
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 336ad75..a24f129 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -161,6 +161,7 @@
 import android.speech.RecognizerIntent;
 import android.telecom.TelecomManager;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.MutableBoolean;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -210,6 +211,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemServiceManager;
 import com.android.server.UiThread;
+import com.android.server.display.BrightnessUtils;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.KeyCombinationManager.TwoKeysCombinationRule;
@@ -2993,6 +2995,12 @@
                     return key_consumed;
                 }
                 break;
+            case KeyEvent.KEYCODE_T:
+                if (down && event.isMetaPressed()) {
+                    toggleTaskbar();
+                    return key_consumed;
+                }
+                break;
             case KeyEvent.KEYCODE_DPAD_UP:
                 if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
@@ -3075,19 +3083,22 @@
                                 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
                                 UserHandle.USER_CURRENT_OR_SELF);
                     }
-                    float min = mPowerManager.getBrightnessConstraint(
-                            PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
-                    float max = mPowerManager.getBrightnessConstraint(
-                            PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
-                    float step = (max - min) / BRIGHTNESS_STEPS * direction;
                     int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
-                    float brightness = mDisplayManager.getBrightness(screenDisplayId);
-                    brightness += step;
-                    // Make sure we don't go beyond the limits.
-                    brightness = Math.min(max, brightness);
-                    brightness = Math.max(min, brightness);
 
-                    mDisplayManager.setBrightness(screenDisplayId, brightness);
+                    float linearBrightness = mDisplayManager.getBrightness(screenDisplayId);
+
+                    float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
+                    float adjustedGammaBrightness =
+                            gammaBrightness + 1f / BRIGHTNESS_STEPS * direction;
+
+                    float adjustedLinearBrightness = BrightnessUtils.convertGammaToLinear(
+                            adjustedGammaBrightness);
+
+                    adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness, 0f,
+                            1f);
+
+                    mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness);
+
                     startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
                             UserHandle.CURRENT_OR_SELF);
                 }
@@ -3664,6 +3675,13 @@
         }
     }
 
+    private void toggleTaskbar() {
+        StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+        if (statusbar != null) {
+            statusbar.toggleTaskbar();
+        }
+    }
+
     private void toggleRecentApps() {
         mPreloadedRecentApps = false; // preloading no longer needs to be canceled
         StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 3d3c5e2..f11c864 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -1743,7 +1743,8 @@
         if (historyDirectory == null) {
             mCheckinFile = null;
             mStatsFile = null;
-            mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
+            mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
         } else {
             mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));
             mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
@@ -10881,14 +10882,17 @@
 
         if (systemDir == null) {
             mStatsFile = null;
-            mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
+            mCheckinFile = null;
+            mDailyFile = null;
+            mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
         } else {
             mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
+            mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
+            mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
             mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
                     mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
         }
-        mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
-        mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
         mStartCount++;
         initTimersAndCounters();
         mOnBattery = mOnBatteryInternal = false;
diff --git a/services/core/java/com/android/server/rollback/OWNERS b/services/core/java/com/android/server/rollback/OWNERS
index 7feb85f..daa0211 100644
--- a/services/core/java/com/android/server/rollback/OWNERS
+++ b/services/core/java/com/android/server/rollback/OWNERS
@@ -1 +1,3 @@
-olilan@google.com
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index ec052ec..efd8b6d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -122,6 +122,10 @@
      */
     void onEmergencyActionLaunchGestureDetected();
 
+    /** Toggle the task bar stash state. */
+    void toggleTaskbar();
+
+    /** Toggle recents. */
     void toggleRecentApps();
 
     void setCurrentUser(int newUserId);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 4489ba9..5bace0e 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -418,6 +418,15 @@
         }
 
         @Override
+        public void toggleTaskbar() {
+            if (mBar != null) {
+                try {
+                    mBar.toggleTaskbar();
+                } catch (RemoteException ex) {}
+            }
+        }
+
+        @Override
         public void toggleRecentApps() {
             if (mBar != null) {
                 try {
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
index 4a6c794..de631bb 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
@@ -65,7 +65,7 @@
  * If a request fails, it retries a number of times with a "short" interval and then resets to the
  * normal interval. The process then repeats.
  *
- * <p>When a valid network time is available, the time is always suggested to the {@link
+ * <p>When a valid network time is available, the network time is always suggested to the {@link
  * com.android.server.timedetector.TimeDetectorService} where it may be used to set the device
  * system clock, depending on user settings and what other signals are available.
  */
@@ -181,24 +181,6 @@
     }
 
     /**
-     * Clears the cached NTP time. For use during tests to simulate when no NTP time is available.
-     *
-     * <p>This operation takes place in the calling thread rather than the service's handler thread.
-     */
-    @RequiresPermission(android.Manifest.permission.SET_TIME)
-    void clearTimeForTests() {
-        mContext.enforceCallingPermission(
-                android.Manifest.permission.SET_TIME, "clear latest network time");
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            mNtpTrustedTime.clearCachedTimeResult();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
      * Forces the service to refresh the NTP time.
      *
      * <p>This operation takes place in the calling thread rather than the service's handler thread.
@@ -286,7 +268,7 @@
         }
 
         /**
-         * Checks if the user prefers to automatically set the time.
+         * Checks if the user prefers to automatically set the device's system clock time.
          */
         private boolean isAutomaticTimeEnabled() {
             ContentResolver resolver = mContext.getContentResolver();
@@ -313,7 +295,7 @@
     }
 
     /**
-     * The interface the service uses to interact with the time refresh logic.
+     * The interface the service uses to interact with the network time refresh logic.
      * Extracted for testing.
      */
     @VisibleForTesting
@@ -387,10 +369,10 @@
         private final NtpTrustedTime mNtpTrustedTime;
 
         /**
-         * Records the time of the last refresh attempt (successful or otherwise) by this service.
-         * This is used when scheduling the next refresh attempt. In cases where {@link
-         * #refreshAndRescheduleIfRequired} is called too frequently, this will prevent each call
-         * resulting in a network request. See also {@link #mShortPollingIntervalMillis}.
+         * Records the elapsed realtime of the last refresh attempt (successful or otherwise) by
+         * this service. This is used when scheduling the next refresh attempt. In cases where
+         * {@link #refreshAndRescheduleIfRequired} is called too frequently, this will prevent each
+         * call resulting in a network request. See also {@link #mShortPollingIntervalMillis}.
          *
          * <p>Time servers are a shared resource and so Android should avoid loading them.
          * Generally, a refresh attempt will succeed and the service won't need to make further
@@ -454,8 +436,11 @@
                 return;
             }
 
-            // Attempt to refresh the network time if there is no latest time result, or if the
-            // latest time result is considered too old.
+            // Step 1: Work out if the latest time result, if any, needs to be refreshed and handle
+            // the refresh.
+
+            // A refresh should be attempted if there is no latest time result, or if the latest
+            // time result is considered too old.
             NtpTrustedTime.TimeResult initialTimeResult = mNtpTrustedTime.getCachedTimeResult();
             boolean shouldAttemptRefresh;
             synchronized (this) {
@@ -472,22 +457,42 @@
             boolean refreshSuccessful = false;
             if (shouldAttemptRefresh) {
                 // This is a blocking call. Deliberately invoked without holding the "this" monitor
-                // to avoid blocking logic that wants to use the "this" monitor.
+                // to avoid blocking other logic that wants to use the "this" monitor, e.g. dump().
                 refreshSuccessful = tryRefresh(network);
             }
 
             synchronized (this) {
-                // Manage mTryAgainCounter.
+                // This section of code deliberately doesn't assume it is the only component using
+                // the NtpTrustedTime singleton to obtain NTP times: another component in the same
+                // process could be gathering NTP signals (which then won't have been suggested to
+                // the time detector).
+                // TODO(b/222295093): Make this class the sole user of the NtpTrustedTime singleton
+                //  and simplify / reduce duplicate suggestions and other logic.
+                NtpTrustedTime.TimeResult latestTimeResult = mNtpTrustedTime.getCachedTimeResult();
+
+                // currentElapsedRealtimeMillis is used to evaluate ages and refresh scheduling
+                // below. Capturing this after obtaining the cached time result ensures that latest
+                // time result ages will be >= 0.
+                long currentElapsedRealtimeMillis = mElapsedRealtimeMillisSupplier.get();
+
+                long latestTimeResultAgeMillis = calculateTimeResultAgeMillis(
+                        latestTimeResult, currentElapsedRealtimeMillis);
+
+                // Step 2: Set mTryAgainCounter.
+                //   + == 0: The last attempt was successful OR the latest time result is acceptable
+                //           OR the mTryAgainCounter exceeded mTryAgainTimesMax and has been reset
+                //           to 0. In all these cases the normal refresh interval should be used.
+                //   + > 0: The last refresh attempt was unsuccessful. Some number of retries are
+                //          allowed using the short interval depending on mTryAgainTimesMax.
                 if (shouldAttemptRefresh) {
                     if (refreshSuccessful) {
-                        // Reset failure tracking.
                         mTryAgainCounter = 0;
                     } else {
                         if (mTryAgainTimesMax < 0) {
                             // When mTryAgainTimesMax is negative there's no enforced maximum and
                             // short intervals should be used until a successful refresh. Setting
                             // mTryAgainCounter to 1 is sufficient for the interval calculations
-                            // below. There's no need to increment.
+                            // below, i.e. there's no need to increment.
                             mTryAgainCounter = 1;
                         } else {
                             mTryAgainCounter++;
@@ -497,58 +502,97 @@
                         }
                     }
                 }
-
-                // currentElapsedRealtimeMillis is used to evaluate ages and refresh scheduling
-                // below. Capturing this after a possible successful refresh ensures that latest
-                // time result ages will be >= 0.
-                long currentElapsedRealtimeMillis = mElapsedRealtimeMillisSupplier.get();
-
-                // This section of code deliberately doesn't assume it is the only component using
-                // mNtpTrustedTime to obtain NTP times: another component in the same process could
-                // be gathering NTP signals (which then won't have been suggested to the time
-                // detector).
-                // TODO(b/222295093): Make this class the sole owner of mNtpTrustedTime and
-                //  simplify / reduce duplicate suggestions.
-                NtpTrustedTime.TimeResult latestTimeResult = mNtpTrustedTime.getCachedTimeResult();
-                long latestTimeResultAgeMillis = calculateTimeResultAgeMillis(
-                        latestTimeResult, currentElapsedRealtimeMillis);
-
-                // Suggest the latest time result to the time detector if it is fresh regardless of
-                // whether refresh happened above.
                 if (latestTimeResultAgeMillis < mNormalPollingIntervalMillis) {
-                    // We assume the time detector service will detect duplicate suggestions and not
-                    // do more work than it has to, so no need to avoid making duplicate
-                    // suggestions.
+                    // The latest time result may indicate a successful refresh has been achieved by
+                    // another user of the NtpTrustedTime singleton. This could be an "else if", but
+                    // this is deliberately done defensively in all cases to maintain the invariant
+                    // that mTryAgainCounter will be 0 if the latest time result is currently ok.
+                    mTryAgainCounter = 0;
+                }
+
+                // Step 3: Suggest the latest time result to the time detector if it is fresh
+                // regardless of whether a refresh happened / succeeded above. The time detector
+                // service can detect duplicate suggestions and not do more work than it has to, so
+                // there is no need to avoid making duplicate suggestions.
+                if (latestTimeResultAgeMillis < mNormalPollingIntervalMillis) {
                     makeNetworkTimeSuggestion(latestTimeResult, reason, refreshCallbacks);
                 }
 
-                // (Re)schedule the next refresh based on the latest state.
-                // Determine which refresh delay to use by using the current value of
-                // mTryAgainCounter. The refresh delay is applied to a different point in time
-                // depending on whether the latest available time result (if any) is still
-                // considered fresh to ensure the delay acts correctly.
-                long refreshDelayMillis = mTryAgainCounter > 0
+                // Step 4: (Re)schedule the next refresh attempt based on the latest state.
+
+                // Determine which refresh attempt delay to use by using the current value of
+                // mTryAgainCounter.
+                long refreshAttemptDelayMillis = mTryAgainCounter > 0
                         ? mShortPollingIntervalMillis : mNormalPollingIntervalMillis;
+
+                // The refresh attempt delay is applied to a different point in time depending on
+                // whether a refresh attempt is overdue to ensure the refresh attempt scheduling
+                // acts correctly / safely, i.e. won't schedule actions for immediate execution or
+                // in the past.
                 long nextRefreshElapsedRealtimeMillis;
-                if (latestTimeResultAgeMillis < mNormalPollingIntervalMillis) {
-                    // The latest time result is fresh, use it to determine when next to refresh.
+                if (latestTimeResultAgeMillis < refreshAttemptDelayMillis) {
+                    // The latestTimeResultAgeMillis and refreshAttemptDelayMillis indicate a
+                    // refresh attempt is not yet due.  This branch uses the elapsed realtime of the
+                    // latest time result to calculate when the latest time result will become too
+                    // old and the next refresh attempt will be due.
+                    //
+                    // Possibilities:
+                    //   + A refresh was attempted and successful, mTryAgainCounter will be set
+                    //     to 0, refreshAttemptDelayMillis == mNormalPollingIntervalMillis, and this
+                    //     branch will execute.
+                    //   + No refresh was attempted, but something else refreshed the latest time
+                    //     result held by the NtpTrustedTime.
+                    //
+                    // If a refresh was attempted but was unsuccessful, latestTimeResultAgeMillis >=
+                    // mNormalPollingIntervalMillis (because otherwise it wouldn't be attempted),
+                    // this branch won't be executed, and the one below will be instead.
                     nextRefreshElapsedRealtimeMillis =
-                            latestTimeResult.getElapsedRealtimeMillis() + refreshDelayMillis;
+                            latestTimeResult.getElapsedRealtimeMillis() + refreshAttemptDelayMillis;
                 } else if (mLastRefreshAttemptElapsedRealtimeMillis != null) {
-                    // The latest time result is missing or old and still needs to be refreshed.
-                    // mLastRefreshAttemptElapsedRealtimeMillis, which should always be set by this
-                    // point because there's no fresh time result, should be very close to
-                    // currentElapsedRealtimeMillis unless the refresh was not allowed.
+                    // This branch is executed when the latest time result is missing, or it's older
+                    // than refreshAttemptDelayMillis. There may already have been attempts to
+                    // refresh the network time that have failed, so the important point for this
+                    // branch is not how old the latest time result is, but when the last refresh
+                    // attempt took place:
+                    //   + If a refresh was just attempted (and failed), then
+                    //     mLastRefreshAttemptElapsedRealtimeMillis will be close to
+                    //     currentElapsedRealtimeMillis.
+                    //   + If a refresh was not just attempted, for a refresh not to have been
+                    //     attempted EITHER:
+                    //     + The latest time result must be < mNormalPollingIntervalMillis ago
+                    //       (would be handled by the branch above)
+                    //     + A refresh wasn't allowed because {time since last refresh attempt}
+                    //       < mShortPollingIntervalMillis, so
+                    //       (mLastRefreshAttemptElapsedRealtimeMillis + refreshAttemptDelayMillis)
+                    //       would have to be in the future regardless of the
+                    //       refreshAttemptDelayMillis value. This ignores the execution time
+                    //       between the "current time" used to work out whether a refresh needed to
+                    //       happen, and "current time" used to compute the last time result age,
+                    //       but a single short interval shouldn't matter.
                     nextRefreshElapsedRealtimeMillis =
-                            mLastRefreshAttemptElapsedRealtimeMillis + refreshDelayMillis;
+                            mLastRefreshAttemptElapsedRealtimeMillis + refreshAttemptDelayMillis;
                 } else {
-                    // This should not happen: mLastRefreshAttemptElapsedRealtimeMillis should
-                    // always be non-null by this point.
-                    logToDebugAndDumpsys(
-                            "mLastRefreshAttemptElapsedRealtimeMillis unexpectedly missing."
-                                    + " Scheduling using currentElapsedRealtimeMillis");
+                    // This branch should never execute: mLastRefreshAttemptElapsedRealtimeMillis
+                    // should always be non-null because a refresh should always be attempted at
+                    // least once above. Regardelss, the calculation below should result in safe
+                    // scheduling behavior.
+                    String logMsg = "mLastRefreshAttemptElapsedRealtimeMillis unexpectedly missing."
+                            + " Scheduling using currentElapsedRealtimeMillis";
+                    Log.w(TAG, logMsg);
+                    logToDebugAndDumpsys(logMsg);
                     nextRefreshElapsedRealtimeMillis =
-                            currentElapsedRealtimeMillis + refreshDelayMillis;
+                            currentElapsedRealtimeMillis + refreshAttemptDelayMillis;
+                }
+
+                // Defensive coding to guard against bad scheduling / logic errors above: Try to
+                // ensure that alarms aren't scheduled in the past.
+                if (nextRefreshElapsedRealtimeMillis <= currentElapsedRealtimeMillis) {
+                    String logMsg = "nextRefreshElapsedRealtimeMillis is a time in the past."
+                            + " Scheduling using currentElapsedRealtimeMillis instead";
+                    Log.w(TAG, logMsg);
+                    logToDebugAndDumpsys(logMsg);
+                    nextRefreshElapsedRealtimeMillis =
+                            currentElapsedRealtimeMillis + refreshAttemptDelayMillis;
                 }
                 refreshCallbacks.scheduleNextRefresh(nextRefreshElapsedRealtimeMillis);
 
@@ -562,7 +606,7 @@
                         + formatElapsedRealtimeMillis(currentElapsedRealtimeMillis)
                         + ", latestTimeResult=" + latestTimeResult
                         + ", mTryAgainCounter=" + mTryAgainCounter
-                        + ", refreshDelayMillis=" + refreshDelayMillis
+                        + ", refreshAttemptDelayMillis=" + refreshAttemptDelayMillis
                         + ", nextRefreshElapsedRealtimeMillis="
                         + formatElapsedRealtimeMillis(nextRefreshElapsedRealtimeMillis));
             }
@@ -592,6 +636,12 @@
             return currentElapsedRealtimeMillis >= nextRefreshAllowedElapsedRealtimeMillis;
         }
 
+        /**
+         * Attempts a network time refresh. Updates {@link
+         * #mLastRefreshAttemptElapsedRealtimeMillis} regardless of the outcome and returns whether
+         * the attempt was successful. The latest successful refresh result can be found in {@link
+         * NtpTrustedTime#getCachedTimeResult()}.
+         */
         private boolean tryRefresh(@NonNull Network network) {
             long currentElapsedRealtimeMillis = mElapsedRealtimeMillisSupplier.get();
             synchronized (this) {
@@ -600,15 +650,18 @@
             return mNtpTrustedTime.forceRefresh(network);
         }
 
-        /** Suggests the time to the time detector. It may choose use it to set the system clock. */
-        private void makeNetworkTimeSuggestion(@NonNull TimeResult ntpResult,
+        /**
+         * Suggests the network time to the time detector. It may choose use it to set the system
+         * clock.
+         */
+        private void makeNetworkTimeSuggestion(@NonNull TimeResult timeResult,
                 @NonNull String debugInfo, @NonNull RefreshCallbacks refreshCallbacks) {
             UnixEpochTime timeSignal = new UnixEpochTime(
-                    ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
+                    timeResult.getElapsedRealtimeMillis(), timeResult.getTimeMillis());
             NetworkTimeSuggestion timeSuggestion =
-                    new NetworkTimeSuggestion(timeSignal, ntpResult.getUncertaintyMillis());
+                    new NetworkTimeSuggestion(timeSignal, timeResult.getUncertaintyMillis());
             timeSuggestion.addDebugInfo(debugInfo);
-            timeSuggestion.addDebugInfo(ntpResult.toString());
+            timeSuggestion.addDebugInfo(timeResult.toString());
             refreshCallbacks.submitSuggestion(timeSuggestion);
         }
 
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
index afc0bdd..cfc95df 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
@@ -37,11 +37,6 @@
     private static final String SHELL_COMMAND_SERVICE_NAME = "network_time_update_service";
 
     /**
-     * A shell command that clears the time signal received from the network.
-     */
-    private static final String SHELL_COMMAND_CLEAR_TIME = "clear_time";
-
-    /**
      * A shell command that forces the time signal to be refreshed from the network.
      */
     private static final String SHELL_COMMAND_FORCE_REFRESH = "force_refresh";
@@ -73,8 +68,6 @@
         }
 
         switch (cmd) {
-            case SHELL_COMMAND_CLEAR_TIME:
-                return runClearTime();
             case SHELL_COMMAND_FORCE_REFRESH:
                 return runForceRefresh();
             case SHELL_COMMAND_SET_SERVER_CONFIG:
@@ -87,11 +80,6 @@
         }
     }
 
-    private int runClearTime() {
-        mNetworkTimeUpdateService.clearTimeForTests();
-        return 0;
-    }
-
     private int runForceRefresh() {
         boolean success = mNetworkTimeUpdateService.forceRefreshForTests();
         getOutPrintWriter().println(success);
@@ -147,8 +135,6 @@
         pw.printf("Network Time Update Service (%s) commands:\n", SHELL_COMMAND_SERVICE_NAME);
         pw.printf("  help\n");
         pw.printf("    Print this help text.\n");
-        pw.printf("  %s\n", SHELL_COMMAND_CLEAR_TIME);
-        pw.printf("    Clears the latest time.\n");
         pw.printf("  %s\n", SHELL_COMMAND_FORCE_REFRESH);
         pw.printf("    Refreshes the latest time. Prints whether it was successful.\n");
         pw.printf("  %s\n", SHELL_COMMAND_SET_SERVER_CONFIG);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 0da967a..22f096b 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -37,6 +37,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.NtpTrustedTime;
@@ -53,6 +54,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.net.InetSocketAddress;
 import java.time.DateTimeException;
 import java.util.Objects;
 
@@ -377,7 +379,7 @@
      *
      * <p>This operation takes place in the calling thread.
      */
-    void clearNetworkTime() {
+    void clearLatestNetworkTime() {
         enforceSuggestNetworkTimePermission();
 
         final long token = Binder.clearCallingIdentity();
@@ -390,12 +392,29 @@
 
     @Override
     public UnixEpochTime latestNetworkTime() {
-        NetworkTimeSuggestion suggestion = getLatestNetworkSuggestion();
-        if (suggestion != null) {
-            return suggestion.getUnixEpochTime();
+        NetworkTimeSuggestion latestNetworkTime;
+        // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+        //  NtpTrustedTime result in a suggestion being made to the time detector.
+        //  mNtpTrustedTime can be removed once this happens.
+        if (TimeDetectorNetworkTimeHelper.isInUse()) {
+            // The new implementation.
+            latestNetworkTime = mTimeDetectorStrategy.getLatestNetworkSuggestion();
         } else {
+            // The old implementation.
+            NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
+            if (ntpResult != null) {
+                latestNetworkTime = new NetworkTimeSuggestion(
+                        new UnixEpochTime(
+                                ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis()),
+                        ntpResult.getUncertaintyMillis());
+            } else {
+                latestNetworkTime = null;
+            }
+        }
+        if (latestNetworkTime == null) {
             throw new ParcelableException(new DateTimeException("Missing network time fix"));
         }
+        return latestNetworkTime.getUnixEpochTime();
     }
 
     /**
@@ -403,23 +422,7 @@
      */
     @Nullable
     NetworkTimeSuggestion getLatestNetworkSuggestion() {
-        // TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can
-        //  be sure that all uses of NtpTrustedTime results in a suggestion being made to the time
-        //  detector. mNtpTrustedTime can be removed once this happens.
-        if (TimeDetectorNetworkTimeHelper.isInUse()) {
-            // The new implementation.
-            return mTimeDetectorStrategy.getLatestNetworkSuggestion();
-        } else {
-            // The old implementation.
-            NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
-            if (ntpResult != null) {
-                UnixEpochTime unixEpochTime = new UnixEpochTime(
-                        ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
-                return new NetworkTimeSuggestion(unixEpochTime, ntpResult.getUncertaintyMillis());
-            } else {
-                return null;
-            }
-        }
+        return mTimeDetectorStrategy.getLatestNetworkSuggestion();
     }
 
     /**
@@ -440,6 +443,57 @@
         mHandler.post(() -> mTimeDetectorStrategy.suggestExternalTime(timeSignal));
     }
 
+    /**
+     * Sets the network time for testing {@link SystemClock#currentNetworkTimeClock()}.
+     *
+     * <p>This operation takes place in the calling thread.
+     */
+    void setNetworkTimeForSystemClockForTests(
+            @NonNull UnixEpochTime unixEpochTime, int uncertaintyMillis) {
+        enforceSuggestNetworkTimePermission();
+
+        // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+        //  NtpTrustedTime result in a suggestion being made to the time detector.
+        //  mNtpTrustedTime can be removed once this happens.
+        if (TimeDetectorNetworkTimeHelper.isInUse()) {
+            NetworkTimeSuggestion suggestion =
+                    new NetworkTimeSuggestion(unixEpochTime, uncertaintyMillis);
+            suggestion.addDebugInfo("Injected for tests");
+            mTimeDetectorStrategy.suggestNetworkTime(suggestion);
+        } else {
+            NtpTrustedTime.TimeResult timeResult = new NtpTrustedTime.TimeResult(
+                    unixEpochTime.getUnixEpochTimeMillis(),
+                    unixEpochTime.getElapsedRealtimeMillis(),
+                    uncertaintyMillis,
+                    InetSocketAddress.createUnresolved("time.set.for.tests", 123));
+            mNtpTrustedTime.setCachedTimeResult(timeResult);
+        }
+    }
+
+    /**
+     * Clears the network time for testing {@link SystemClock#currentNetworkTimeClock()}.
+     *
+     * <p>This operation takes place in the calling thread.
+     */
+    void clearNetworkTimeForSystemClockForTests() {
+        enforceSuggestNetworkTimePermission();
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+            //  NtpTrustedTime result in a suggestion being made to the time detector.
+            //  mNtpTrustedTime can be removed once this happens.
+            if (TimeDetectorNetworkTimeHelper.isInUse()) {
+                // Clear the latest network suggestion. Done in all c
+                mTimeDetectorStrategy.clearLatestNetworkSuggestion();
+            } else {
+                mNtpTrustedTime.clearCachedTimeResult();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     @Override
     protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
             @Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
index cce5709..fe0127f 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
@@ -16,12 +16,14 @@
 package com.android.server.timedetector;
 
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CLEAR_NETWORK_TIME;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CONFIRM_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_NETWORK_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_TIME_STATE;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SERVICE_NAME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_TIME_STATE;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_EXTERNAL_TIME;
 import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_GNSS_TIME;
@@ -73,9 +75,9 @@
             case SHELL_COMMAND_SUGGEST_NETWORK_TIME:
                 return runSuggestNetworkTime();
             case SHELL_COMMAND_GET_NETWORK_TIME:
-                return runGetNetworkTime();
+                return runGetLatestNetworkTime();
             case SHELL_COMMAND_CLEAR_NETWORK_TIME:
-                return runClearNetworkTime();
+                return runClearLatestNetworkTime();
             case SHELL_COMMAND_SUGGEST_GNSS_TIME:
                 return runSuggestGnssTime();
             case SHELL_COMMAND_SUGGEST_EXTERNAL_TIME:
@@ -86,6 +88,10 @@
                 return runSetTimeState();
             case SHELL_COMMAND_CONFIRM_TIME:
                 return runConfirmTime();
+            case SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME:
+                return runClearSystemClockNetworkTime();
+            case SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME:
+                return runSetSystemClockNetworkTime();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -128,15 +134,15 @@
                 mInterface::suggestNetworkTime);
     }
 
-    private int runGetNetworkTime() {
+    private int runGetLatestNetworkTime() {
         NetworkTimeSuggestion networkTimeSuggestion = mInterface.getLatestNetworkSuggestion();
         final PrintWriter pw = getOutPrintWriter();
         pw.println(networkTimeSuggestion);
         return 0;
     }
 
-    private int runClearNetworkTime() {
-        mInterface.clearNetworkTime();
+    private int runClearLatestNetworkTime() {
+        mInterface.clearLatestNetworkTime();
         return 0;
     }
 
@@ -187,6 +193,20 @@
         return 0;
     }
 
+    private int runClearSystemClockNetworkTime() {
+        mInterface.clearNetworkTimeForSystemClockForTests();
+        return 0;
+    }
+
+    private int runSetSystemClockNetworkTime() {
+        NetworkTimeSuggestion networkTimeSuggestion =
+                NetworkTimeSuggestion.parseCommandLineArg(this);
+        mInterface.setNetworkTimeForSystemClockForTests(
+                networkTimeSuggestion.getUnixEpochTime(),
+                networkTimeSuggestion.getUncertaintyMillis());
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -218,6 +238,16 @@
         pw.printf("    Prints the network time information held by the detector.\n");
         pw.printf("  %s\n", SHELL_COMMAND_CLEAR_NETWORK_TIME);
         pw.printf("    Clears the network time information held by the detector.\n");
+        // TODO(b/222295093) Remove these "system_clock" commands when
+        //  SystemClock.currentNetworkTimeClock() is guaranteed to use the latest network
+        //  suggestion. Then, commands above can be used instead.
+        pw.printf("  %s <network suggestion opts>\n",
+                SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME);
+        pw.printf("    Sets the network time information used for"
+                + " SystemClock.currentNetworkTimeClock().\n");
+        pw.printf("  %s\n", SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME);
+        pw.printf("    Clears the network time information used for"
+                + " SystemClock.currentNetworkTimeClock().\n");
         pw.println();
         ManualTimeSuggestion.printCommandLineOpts(pw);
         pw.println();
diff --git a/services/core/java/com/android/server/utils/Slogf.java b/services/core/java/com/android/server/utils/Slogf.java
index 6efbd89..a4b2bfb 100644
--- a/services/core/java/com/android/server/utils/Slogf.java
+++ b/services/core/java/com/android/server/utils/Slogf.java
@@ -30,11 +30,13 @@
 /**
  * Extends {@link Slog} by providing overloaded methods that take string formatting.
  *
- * <p><strong>Note: </strong>the overloaded methods won't create the formatted message if the
- * respective logging level is disabled for the tag, but the compiler will still create an
- * intermediate array of the objects for the {@code vargars}, which could affect garbage collection.
- * So, if you're calling these method in a critical path, make sure to explicitly check for the
- * level before calling them.
+ * <p><strong>Note: </strong>Like the other logging classes, e.g. {@link Log} and {@link Slog}, the
+ * methods in this class log unconditionally regardless of {@link Log#isLoggable(String, int)}.
+ * Therefore, these methods exist just for the convenience of handling formatting.  (Even if they
+ * did check {@link Log#isLoggable(String, int)} before formatting and logging, calling a varargs
+ * method in Java still involves an array allocation.)  If you need to avoid the overhead of logging
+ * on a performance-critical path, either don't use logging in that place, or make the logging
+ * conditional on a static boolean defaulting to false.
  */
 public final class Slogf {
 
@@ -56,11 +58,6 @@
         throw new UnsupportedOperationException("provides only static methods");
     }
 
-    /** Same as {@link Log#isLoggable(String, int)}. */
-    public static boolean isLoggable(String tag, int level) {
-        return Log.isLoggable(tag, level);
-    }
-
     /** Same as {@link Slog#v(String, String)}. */
     public static int v(String tag, String msg) {
         return Slog.v(tag, msg);
@@ -146,166 +143,62 @@
         return Slog.println(priority, tag, msg);
     }
 
-    /**
-     * Logs a {@link Log.VERBOSE} message.
-     *
-     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging
-     * is enabled for the given {@code tag}, but the compiler will still create an intermediate
-     * array of the objects for the {@code vargars}, which could affect garbage collection. So, if
-     * you're calling this method in a critical path, make sure to explicitly do the check before
-     * calling it.
-     */
+    /** Logs a {@link Log.VERBOSE} message. */
     public static void v(String tag, String format, @Nullable Object... args) {
-        if (!isLoggable(tag, Log.VERBOSE)) return;
-
         v(tag, getMessage(format, args));
     }
 
-    /**
-     * Logs a {@link Log.VEBOSE} message with a throwable
-     *
-     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging
-     * is enabled for the given {@code tag}, but the compiler will still create an intermediate
-     * array of the objects for the {@code vargars}, which could affect garbage collection. So, if
-     * you're calling this method in a critical path, make sure to explicitly do the check before
-     * calling it.
-     */
+    /** Logs a {@link Log.VERBOSE} message with a throwable. */
     public static void v(String tag, Throwable throwable, String format, @Nullable Object... args) {
-        if (!isLoggable(tag, Log.VERBOSE)) return;
-
         v(tag, getMessage(format, args), throwable);
     }
 
-    /**
-     * Logs a {@link Log.DEBUG} message.
-     *
-     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging is
-     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
-     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
-     * calling this method in a critical path, make sure to explicitly do the check before calling
-     * it.
-     */
+    /** Logs a {@link Log.DEBUG} message. */
     public static void d(String tag, String format, @Nullable Object... args) {
-        if (!isLoggable(tag, Log.DEBUG)) return;
-
         d(tag, getMessage(format, args));
     }
 
-    /**
-     * Logs a {@link Log.DEBUG} message with a throwable
-     *
-     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging
-     * is enabled for the given {@code tag}, but the compiler will still create an intermediate
-     * array of the objects for the {@code vargars}, which could affect garbage collection. So, if
-     * you're calling this method in a critical path, make sure to explicitly do the check before
-     * calling it.
-     */
+    /** Logs a {@link Log.DEBUG} message with a throwable. */
     public static void d(String tag, Throwable throwable, String format, @Nullable Object... args) {
-        if (!isLoggable(tag, Log.DEBUG)) return;
-
         d(tag, getMessage(format, args), throwable);
     }
 
-    /**
-     * Logs a {@link Log.INFO} message.
-     *
-     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging is
-     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
-     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
-     * calling this method in a critical path, make sure to explicitly do the check before calling
-     * it.
-     */
+    /** Logs a {@link Log.INFO} message. */
     public static void i(String tag, String format, @Nullable Object... args) {
-        if (!isLoggable(tag, Log.INFO)) return;
-
         i(tag, getMessage(format, args));
     }
 
-    /**
-     * Logs a {@link Log.INFO} message with a throwable
-     *
-     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging
-     * is enabled for the given {@code tag}, but the compiler will still create an intermediate
-     * array of the objects for the {@code vargars}, which could affect garbage collection. So, if
-     * you're calling this method in a critical path, make sure to explicitly do the check before
-     * calling it.
-     */
+    /** Logs a {@link Log.INFO} message with a throwable. */
     public static void i(String tag, Throwable throwable, String format, @Nullable Object... args) {
-        if (!isLoggable(tag, Log.INFO)) return;
-
         i(tag, getMessage(format, args), throwable);
     }
 
-    /**
-     * Logs a {@link Log.WARN} message.
-     *
-     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
-     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
-     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
-     * calling this method in a critical path, make sure to explicitly do the check before calling
-     * it.
-     */
+    /** Logs a {@link Log.WARN} message. */
     public static void w(String tag, String format, @Nullable Object... args) {
-        if (!isLoggable(tag, Log.WARN)) return;
-
         w(tag, getMessage(format, args));
     }
 
-    /**
-     * Logs a {@link Log.WARN} message with a throwable
-     *
-     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
-     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
-     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
-     * calling this method in a critical path, make sure to explicitly do the check before calling
-     * it.
-     */
+    /** Logs a {@link Log.WARN} message with a throwable. */
     public static void w(String tag, Throwable throwable, String format, @Nullable Object... args) {
-        if (!isLoggable(tag, Log.WARN)) return;
-
         w(tag, getMessage(format, args), throwable);
     }
 
-    /**
-     * Logs a {@link Log.ERROR} message.
-     *
-     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#ERROR} logging is
-     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
-     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
-     * calling this method in a critical path, make sure to explicitly do the check before calling
-     * it.
-     */
+    /** Logs a {@link Log.ERROR} message. */
     public static void e(String tag, String format, @Nullable Object... args) {
-        if (!isLoggable(tag, Log.ERROR)) return;
-
         e(tag, getMessage(format, args));
     }
 
-    /**
-     * Logs a {@link Log.ERROR} message with a throwable
-     *
-     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#ERROR} logging is
-     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
-     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
-     * calling this method in a critical path, make sure to explicitly do the check before calling
-     * it.
-     */
+    /** Logs a {@link Log.ERROR} message with a throwable. */
     public static void e(String tag, Throwable throwable, String format, @Nullable Object... args) {
-        if (!isLoggable(tag, Log.ERROR)) return;
-
         e(tag, getMessage(format, args), throwable);
     }
 
-    /**
-     * Logs a {@code wtf} message.
-     */
+    /** Logs a {@code wtf} message. */
     public static void wtf(String tag, String format, @Nullable Object... args) {
         wtf(tag, getMessage(format, args));
     }
 
-    /**
-     * Logs a {@code wtf} message with a throwable.
-     */
+    /** Logs a {@code wtf} message with a throwable. */
     public static void wtf(String tag, Throwable throwable, String format,
             @Nullable Object... args) {
         wtf(tag, getMessage(format, args), throwable);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index a0d8dfb..9aa7e56 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -19,7 +19,6 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CALLBACK;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK;
 import static android.os.Build.IS_USER;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
@@ -85,7 +84,6 @@
 import android.util.TypedValue;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
-import android.view.InsetsSource;
 import android.view.MagnificationSpec;
 import android.view.Surface;
 import android.view.Surface.OutOfResourcesException;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d21274a..e62d305 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -344,6 +344,7 @@
 import android.window.WindowContainerToken;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.ReferrerIntent;
@@ -508,7 +509,10 @@
     private RemoteTransition mPendingRemoteTransition;
     ActivityOptions returningOptions; // options that are coming back via convertToTranslucent
     AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity
+    @GuardedBy("this")
     ActivityServiceConnectionsHolder mServiceConnectionsHolder; // Service connections.
+    /** @see android.content.Context#BIND_ADJUST_WITH_ACTIVITY */
+    volatile boolean mVisibleForServiceConnection;
     UriPermissionOwner uriPermissions; // current special URI access perms.
     WindowProcessController app;      // if non-null, hosting application
     private State mState;    // current state we are in
@@ -557,7 +561,8 @@
     long lastLaunchTime;    // time of last launch of this activity
     ComponentName requestedVrComponent; // the requested component for handling VR mode.
 
-    boolean inHistory;  // are we in the history task?
+    /** Whether this activity is reachable from hierarchy. */
+    volatile boolean inHistory;
     final ActivityTaskSupervisor mTaskSupervisor;
     final RootWindowContainer mRootWindowContainer;
 
@@ -1917,7 +1922,9 @@
         }
     }
 
-    static @Nullable ActivityRecord forTokenLocked(IBinder token) {
+    /** Gets the corresponding record by the token. Note that it may not exist in the hierarchy. */
+    @Nullable
+    static ActivityRecord forToken(IBinder token) {
         if (token == null) return null;
         final Token activityToken;
         try {
@@ -1926,7 +1933,11 @@
             Slog.w(TAG, "Bad activity token: " + token, e);
             return null;
         }
-        final ActivityRecord r = activityToken.mActivityRef.get();
+        return activityToken.mActivityRef.get();
+    }
+
+    static @Nullable ActivityRecord forTokenLocked(IBinder token) {
+        final ActivityRecord r = forToken(token);
         return r == null || r.getRootTask() == null ? null : r;
     }
 
@@ -4059,17 +4070,32 @@
         mDisplayContent.getDisplayPolicy().removeRelaunchingApp(this);
     }
 
+    ActivityServiceConnectionsHolder getOrCreateServiceConnectionsHolder() {
+        synchronized (this) {
+            if (mServiceConnectionsHolder == null) {
+                mServiceConnectionsHolder = new ActivityServiceConnectionsHolder(this);
+            }
+            return mServiceConnectionsHolder;
+        }
+    }
+
     /**
      * Perform clean-up of service connections in an activity record.
      */
     private void cleanUpActivityServices() {
-        if (mServiceConnectionsHolder == null) {
-            return;
+        synchronized (this) {
+            if (mServiceConnectionsHolder == null) {
+                return;
+            }
+            // Throw away any services that have been bound by this activity.
+            mServiceConnectionsHolder.disconnectActivityFromServices();
+            // This activity record is removing, make sure not to disconnect twice.
+            mServiceConnectionsHolder = null;
         }
-        // Throw away any services that have been bound by this activity.
-        mServiceConnectionsHolder.disconnectActivityFromServices();
-        // This activity record is removing, make sure not to disconnect twice.
-        mServiceConnectionsHolder = null;
+    }
+
+    private void updateVisibleForServiceConnection() {
+        mVisibleForServiceConnection = mVisibleRequested || mState == RESUMED || mState == PAUSING;
     }
 
     /**
@@ -5155,6 +5181,7 @@
     boolean setVisibleRequested(boolean visible) {
         if (!super.setVisibleRequested(visible)) return false;
         setInsetsFrozen(!visible);
+        updateVisibleForServiceConnection();
         if (app != null) {
             mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
         }
@@ -5660,6 +5687,7 @@
                 return;
             }
         }
+        updateVisibleForServiceConnection();
         if (app != null) {
             mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
         }
diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 0859d40..5f56af7 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -16,14 +16,14 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ActivityRecord.State.PAUSING;
-import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 
 import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
@@ -38,8 +38,6 @@
  */
 public class ActivityServiceConnectionsHolder<T> {
 
-    private final ActivityTaskManagerService mService;
-
     /** The activity the owns this service connection object. */
     private final ActivityRecord mActivity;
 
@@ -49,19 +47,19 @@
      * on the WM side since we don't perform operations on the object. Mainly here for communication
      * and booking with the AM side.
      */
+    @GuardedBy("mActivity")
     private ArraySet<T> mConnections;
 
     /** Whether all connections of {@link #mActivity} are being removed. */
     private volatile boolean mIsDisconnecting;
 
-    ActivityServiceConnectionsHolder(ActivityTaskManagerService service, ActivityRecord activity) {
-        mService = service;
+    ActivityServiceConnectionsHolder(ActivityRecord activity) {
         mActivity = activity;
     }
 
     /** Adds a connection record that the activity has bound to a specific service. */
     public void addConnection(T c) {
-        synchronized (mService.mGlobalLock) {
+        synchronized (mActivity) {
             if (mIsDisconnecting) {
                 // This is unlikely to happen because the caller should create a new holder.
                 if (DEBUG_CLEANUP) {
@@ -79,7 +77,7 @@
 
     /** Removed a connection record between the activity and a specific service. */
     public void removeConnection(T c) {
-        synchronized (mService.mGlobalLock) {
+        synchronized (mActivity) {
             if (mConnections == null) {
                 return;
             }
@@ -90,20 +88,18 @@
         }
     }
 
+    /** @see android.content.Context#BIND_ADJUST_WITH_ACTIVITY */
     public boolean isActivityVisible() {
-        synchronized (mService.mGlobalLock) {
-            return mActivity.isVisibleRequested() || mActivity.isState(RESUMED, PAUSING);
-        }
+        return mActivity.mVisibleForServiceConnection;
     }
 
     public int getActivityPid() {
-        synchronized (mService.mGlobalLock) {
-            return mActivity.hasProcess() ? mActivity.app.getPid() : -1;
-        }
+        final WindowProcessController wpc = mActivity.app;
+        return wpc != null ? wpc.getPid() : -1;
     }
 
     public void forEachConnection(Consumer<T> consumer) {
-        synchronized (mService.mGlobalLock) {
+        synchronized (mActivity) {
             if (mConnections == null || mConnections.isEmpty()) {
                 return;
             }
@@ -118,6 +114,7 @@
      * general, this method is used to clean up if the activity didn't unbind services before it
      * is destroyed.
      */
+    @GuardedBy("mActivity")
     void disconnectActivityFromServices() {
         if (mConnections == null || mConnections.isEmpty() || mIsDisconnecting) {
             return;
@@ -130,16 +127,14 @@
         // still in the message queue, so keep the reference of {@link #mConnections} to make sure
         // the connection list is up-to-date.
         mIsDisconnecting = true;
-        mService.mH.post(() -> {
-            mService.mAmInternal.disconnectActivityFromServices(this);
+        mActivity.mAtmService.mH.post(() -> {
+            mActivity.mAtmService.mAmInternal.disconnectActivityFromServices(this);
             mIsDisconnecting = false;
         });
     }
 
     public void dump(PrintWriter pw, String prefix) {
-        synchronized (mService.mGlobalLock) {
-            pw.println(prefix + "activity=" + mActivity);
-        }
+        pw.println(prefix + "activity=" + mActivity);
     }
 
     /** Used by {@link ActivityRecord#dump}. */
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8d671f7..18be2a3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -65,6 +65,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
@@ -201,6 +202,7 @@
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.Process;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.StrictMode;
@@ -227,7 +229,6 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.IRecentsAnimationRunner;
-import android.view.IWindowFocusObserver;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
@@ -1852,11 +1853,11 @@
 
     @Override
     public BackNavigationInfo startBackNavigation(
-            IWindowFocusObserver observer, BackAnimationAdapter adapter) {
+            RemoteCallback navigationObserver, BackAnimationAdapter adapter) {
         mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
                 "startBackNavigation()");
 
-        return mBackNavigationController.startBackNavigation(observer, adapter);
+        return mBackNavigationController.startBackNavigation(navigationObserver, adapter);
     }
 
     /**
@@ -2034,7 +2035,20 @@
             return;
         }
 
-        if (r.moveFocusableActivityToTop("setFocusedTask")) {
+        final Transition transition = (getTransitionController().isCollecting()
+                || !getTransitionController().isShellTransitionsEnabled()) ? null
+                : getTransitionController().createTransition(TRANSIT_TO_FRONT);
+        if (transition != null) {
+            // Set ready before doing anything. If order does change, then that will set it unready
+            // so that we wait for the new lifecycles to complete.
+            transition.setReady(task, true /* ready */);
+        }
+        final boolean movedToTop = r.moveFocusableActivityToTop("setFocusedTask");
+        if (movedToTop) {
+            if (transition != null) {
+                getTransitionController().requestStartTransition(
+                        transition, null /* startTask */, null /* remote */, null /* display */);
+            }
             mRootWindowContainer.resumeFocusedTasksTopActivities();
         } else if (touchedActivity != null && touchedActivity.isFocusable()) {
             final TaskFragment parent = touchedActivity.getTaskFragment();
@@ -2046,6 +2060,10 @@
                         true /* updateInputWindows */);
             }
         }
+        if (transition != null && !movedToTop) {
+            // No order changes and focus-changes, alone, aren't captured in transitions.
+            transition.abort();
+        }
     }
 
     @Override
@@ -6081,18 +6099,11 @@
 
         @Override
         public ActivityServiceConnectionsHolder getServiceConnectionsHolder(IBinder token) {
-            synchronized (mGlobalLock) {
-                final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
-                if (r == null) {
-                    return null;
-                }
-                if (r.mServiceConnectionsHolder == null) {
-                    r.mServiceConnectionsHolder = new ActivityServiceConnectionsHolder(
-                            ActivityTaskManagerService.this, r);
-                }
-
-                return r.mServiceConnectionsHolder;
+            final ActivityRecord r = ActivityRecord.forToken(token);
+            if (r == null || !r.inHistory) {
+                return null;
             }
+            return r.getOrCreateServiceConnectionsHolder();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 75c85d7..4e94f96 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -253,7 +253,7 @@
 
         ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps;
         ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps;
-        if (mDisplayContent.mAtmService.mBackNavigationController.isWaitBackTransition()) {
+        if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringTransition()) {
             tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps);
             tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps);
             if (mDisplayContent.mAtmService.mBackNavigationController
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index bc5f67b..a229fc5 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -41,7 +41,6 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
-import android.view.IWindowFocusObserver;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.WindowInsets;
@@ -66,11 +65,11 @@
 class BackNavigationController {
     private static final String TAG = "BackNavigationController";
     private WindowManagerService mWindowManagerService;
-    private IWindowFocusObserver mFocusObserver;
     private boolean mBackAnimationInProgress;
     private @BackNavigationInfo.BackTargetType int mLastBackType;
     private boolean mShowWallpaper;
     private Runnable mPendingAnimation;
+    private final NavigationMonitor mNavigationMonitor = new NavigationMonitor();
 
     private AnimationHandler mAnimationHandler;
     private final ArrayList<WindowContainer> mTmpOpenApps = new ArrayList<>();
@@ -86,6 +85,11 @@
         return SystemProperties.getInt("persist.wm.debug.predictive_back_screenshot", 0) != 0;
     }
 
+    // Notify focus window changed
+    void onFocusChanged(WindowState newFocus) {
+        mNavigationMonitor.onFocusWindowChanged(newFocus);
+    }
+
     /**
      * Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming
      * back gesture animation.
@@ -96,13 +100,12 @@
      */
     @VisibleForTesting
     @Nullable
-    BackNavigationInfo startBackNavigation(
-            IWindowFocusObserver observer, BackAnimationAdapter adapter) {
+    BackNavigationInfo startBackNavigation(@NonNull RemoteCallback navigationObserver,
+            BackAnimationAdapter adapter) {
         if (!sPredictBackEnable) {
             return null;
         }
         final WindowManagerService wmService = mWindowManagerService;
-        mFocusObserver = observer;
 
         int backType = BackNavigationInfo.TYPE_UNDEFINED;
 
@@ -202,9 +205,7 @@
                     backType = BackNavigationInfo.TYPE_CALLBACK;
                 }
                 infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback());
-                if (mFocusObserver != null) {
-                    window.registerFocusObserver(mFocusObserver);
-                }
+                mNavigationMonitor.startMonitor(window, navigationObserver);
             }
 
             ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
@@ -228,10 +229,8 @@
                     || currentActivity.isActivityTypeHome()
                     || currentActivity.mHasSceneTransition) {
                 infoBuilder.setType(BackNavigationInfo.TYPE_CALLBACK);
-                final WindowState finalFocusedWindow = window;
                 infoBuilder.setOnBackNavigationDone(new RemoteCallback(result ->
-                        onBackNavigationDone(result, finalFocusedWindow,
-                                BackNavigationInfo.TYPE_CALLBACK)));
+                        onBackNavigationDone(result, BackNavigationInfo.TYPE_CALLBACK)));
                 mLastBackType = BackNavigationInfo.TYPE_CALLBACK;
                 return infoBuilder.build();
             }
@@ -334,16 +333,19 @@
         WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer;
         if (finalRemovedWindowContainer != null) {
             final int finalBackType = backType;
-            final WindowState finalFocusedWindow = window;
             RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone(
-                    result, finalFocusedWindow, finalBackType));
+                    result, finalBackType));
             infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
         }
         mLastBackType = backType;
         return infoBuilder.build();
     }
 
-    boolean isWaitBackTransition() {
+    boolean isMonitoringTransition() {
+        return isWaitBackTransition() || mNavigationMonitor.isMonitoring();
+    }
+
+    private boolean isWaitBackTransition() {
         return mAnimationHandler.mComposed && mAnimationHandler.mWaitTransition;
     }
 
@@ -363,11 +365,23 @@
      */
     boolean removeIfContainsBackAnimationTargets(ArraySet<ActivityRecord> openApps,
             ArraySet<ActivityRecord> closeApps) {
-        if (!isWaitBackTransition()) {
+        if (!isMonitoringTransition()) {
             return false;
         }
         mTmpCloseApps.addAll(closeApps);
-        boolean result = false;
+        final boolean matchAnimationTargets = removeIfWaitForBackTransition(openApps, closeApps);
+        if (!matchAnimationTargets) {
+            mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
+        }
+        mTmpCloseApps.clear();
+        return matchAnimationTargets;
+    }
+
+    boolean removeIfWaitForBackTransition(ArraySet<ActivityRecord> openApps,
+            ArraySet<ActivityRecord> closeApps) {
+        if (!isWaitBackTransition()) {
+            return false;
+        }
         // Note: TmpOpenApps is empty. Unlike shell transition, the open apps will be removed from
         // mOpeningApps if there is no visibility change.
         if (mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps)) {
@@ -386,10 +400,76 @@
                     closeApps.removeAt(i);
                 }
             }
-            result = true;
+            return true;
         }
-        mTmpCloseApps.clear();
-        return result;
+        return false;
+    }
+
+    private static class NavigationMonitor {
+        // The window which triggering the back navigation.
+        private WindowState mNavigatingWindow;
+        private RemoteCallback mObserver;
+
+        void startMonitor(@NonNull WindowState window, @NonNull RemoteCallback observer) {
+            mNavigatingWindow = window;
+            mObserver = observer;
+        }
+
+        void stopMonitor() {
+            mNavigatingWindow = null;
+            mObserver = null;
+        }
+
+        boolean isMonitoring() {
+            return mNavigatingWindow != null && mObserver != null;
+        }
+
+        /**
+         * Notify focus window changed during back navigation. This will cancel the gesture for
+         * scenarios like: a system window popup, or when an activity add a new window.
+         *
+         * This method should only be used to check window-level change, otherwise it may cause
+         * misjudgment in multi-window mode. For example: in split-screen, when user is
+         * navigating on the top task, bottom task can start a new task, which will gain focus for
+         * a short time, but we should not cancel the navigation.
+         */
+        private void onFocusWindowChanged(WindowState newFocus) {
+            if (!isMonitoring() || !atSameDisplay(newFocus)) {
+                return;
+            }
+            // Keep navigating if either new focus == navigating window or null.
+            if (newFocus != null && newFocus != mNavigatingWindow
+                    && (newFocus.mActivityRecord == null
+                    || (newFocus.mActivityRecord == mNavigatingWindow.mActivityRecord))) {
+                EventLogTags.writeWmBackNaviCanceled("focusWindowChanged");
+                mObserver.sendResult(null /* result */);
+            }
+        }
+
+        /**
+         * Notify an unexpected transition has happened during back navigation.
+         */
+        private void onTransitionReadyWhileNavigate(ArrayList<WindowContainer> opening,
+                ArrayList<WindowContainer> closing) {
+            if (!isMonitoring()) {
+                return;
+            }
+            final ArrayList<WindowContainer> all = new ArrayList<>(opening);
+            all.addAll(closing);
+            for (WindowContainer app : all) {
+                if (app.hasChild(mNavigatingWindow)) {
+                    EventLogTags.writeWmBackNaviCanceled("transitionHappens");
+                    mObserver.sendResult(null /* result */);
+                    break;
+                }
+            }
+
+        }
+
+        private boolean atSameDisplay(WindowState newFocus) {
+            final int navigatingDisplayId = mNavigatingWindow.getDisplayId();
+            return newFocus == null || newFocus.getDisplayId() == navigatingDisplayId;
+        }
     }
 
     // For shell transition
@@ -403,8 +483,7 @@
      *  animations, and shouldn't join next transition.
      */
     boolean containsBackAnimationTargets(Transition transition) {
-        if (!mAnimationHandler.mComposed
-                || (transition.mType != TRANSIT_CLOSE && transition.mType != TRANSIT_TO_BACK)) {
+        if (!isMonitoringTransition()) {
             return false;
         }
         final ArraySet<WindowContainer> targets = transition.mParticipants;
@@ -420,19 +499,19 @@
                 mTmpCloseApps.add(wc);
             }
         }
-        final boolean result = mAnimationHandler.containsBackAnimationTargets(
-                mTmpOpenApps, mTmpCloseApps);
-        if (result) {
-            mAnimationHandler.mOpenTransitionTargetMatch =
-                    mAnimationHandler.containTarget(mTmpOpenApps, true);
+        final boolean matchAnimationTargets = isWaitBackTransition()
+                && (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK)
+                && mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+        if (!matchAnimationTargets) {
+            mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
         }
         mTmpOpenApps.clear();
         mTmpCloseApps.clear();
-        return result;
+        return matchAnimationTargets;
     }
 
     boolean isMonitorTransitionTarget(WindowContainer wc) {
-        if (!mAnimationHandler.mComposed || !mAnimationHandler.mWaitTransition) {
+        if (!isWaitBackTransition()) {
             return false;
         }
         return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
@@ -951,16 +1030,13 @@
         }
     }
 
-    private void onBackNavigationDone(Bundle result, WindowState focusedWindow, int backType) {
+    private void onBackNavigationDone(Bundle result, int backType) {
         boolean triggerBack = result != null && result.getBoolean(
                 BackNavigationInfo.KEY_TRIGGER_BACK);
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
                 + "triggerBack=%b", backType, triggerBack);
 
-        if (mFocusObserver != null) {
-            focusedWindow.unregisterFocusObserver(mFocusObserver);
-            mFocusObserver = null;
-        }
+        mNavigationMonitor.stopMonitor();
         mBackAnimationInProgress = false;
         mShowWallpaper = false;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 17ec9cb..86c4e0f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3809,6 +3809,7 @@
         }
 
         getDisplayPolicy().focusChangedLw(oldFocus, newFocus);
+        mAtmService.mBackNavigationController.onFocusChanged(newFocus);
 
         if (imWindowChanged && oldFocus != mInputMethodWindow) {
             // Focus of the input method window changed. Perform layout if needed.
@@ -4589,8 +4590,7 @@
      */
     @VisibleForTesting
     SurfaceControl computeImeParent() {
-        if (!ImeTargetVisibilityPolicy.isValidToComputeImeParent(mImeLayeringTarget,
-                mImeInputTarget)) {
+        if (!ImeTargetVisibilityPolicy.canComputeImeParent(mImeLayeringTarget, mImeInputTarget)) {
             return null;
         }
         // Attach it to app if the target is part of an app and such app is covering the entire
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 2b34660..ce3379e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -931,7 +931,7 @@
         }
 
         final InsetsSourceProvider provider = win.getControllableInsetProvider();
-        if (provider != null && provider.getSource().getInsetsRoundedCornerFrame()
+        if (provider != null && provider.getSource().insetsRoundedCornerFrame()
                 != attrs.insetsRoundedCornerFrame) {
             provider.getSource().setInsetsRoundedCornerFrame(attrs.insetsRoundedCornerFrame);
         }
@@ -1364,9 +1364,8 @@
         applyKeyguardPolicy(win, imeTarget);
 
         // Check if the freeform window overlaps with the navigation bar area.
-        final boolean isOverlappingWithNavBar = isOverlappingWithNavBar(win);
-        if (isOverlappingWithNavBar && !mIsFreeformWindowOverlappingWithNavBar
-                && win.inFreeformWindowingMode()) {
+        if (!mIsFreeformWindowOverlappingWithNavBar && win.inFreeformWindowingMode()
+                && win.mActivityRecord != null && isOverlappingWithNavBar(win)) {
             mIsFreeformWindowOverlappingWithNavBar = true;
         }
 
@@ -1454,7 +1453,7 @@
             // mode; if it's in gesture navigation mode, the navigation bar will be
             // NAV_BAR_FORCE_TRANSPARENT and its appearance won't be decided by overlapping
             // windows.
-            if (isOverlappingWithNavBar) {
+            if (isOverlappingWithNavBar(win)) {
                 if (mNavBarColorWindowCandidate == null) {
                     mNavBarColorWindowCandidate = win;
                     addSystemBarColorApp(win);
@@ -1482,7 +1481,7 @@
                     addSystemBarColorApp(win);
                 }
             }
-            if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) {
+            if (isOverlappingWithNavBar(win) && mNavBarColorWindowCandidate == null) {
                 mNavBarColorWindowCandidate = win;
             }
         }
@@ -2591,6 +2590,7 @@
         lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+        lp.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setFitInsetsTypes(0);
         lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         if (ActivityManager.isHighEndGfx()) {
@@ -2646,7 +2646,7 @@
 
     @VisibleForTesting
     static boolean isOverlappingWithNavBar(@NonNull WindowState win) {
-        if (win.mActivityRecord == null || !win.isVisible()) {
+        if (!win.isVisible()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index 031e022..594929b 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -66,6 +66,8 @@
 # bootanim finished:
 31007 wm_boot_animation_done (time|2|3)
 
+# Back navigation.
+31100 wm_back_navi_canceled (Reason|3)
 
 # IME surface parent is updated.
 32003 imf_update_ime_parent (surface name|3)
diff --git a/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java b/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java
index 471bdf2..71dd917 100644
--- a/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java
+++ b/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java
@@ -55,11 +55,14 @@
      * @param imeInputTarget The window which start the input connection, receive input from IME.
      * @return {@code true} to keep computing the ime parent, {@code false} to defer this operation
      */
-    public static boolean isValidToComputeImeParent(@Nullable WindowState imeLayeringTarget,
+    public static boolean canComputeImeParent(@Nullable WindowState imeLayeringTarget,
             @Nullable InputTarget imeInputTarget) {
         if (imeLayeringTarget == null) {
             return false;
         }
+        if (shouldComputeImeParentForEmbeddedActivity(imeLayeringTarget, imeInputTarget)) {
+            return true;
+        }
         // Ensure changing the IME parent when the layering target that may use IME has
         // became to the input target for preventing IME flickers.
         // Note that:
@@ -72,9 +75,6 @@
         boolean imeLayeringTargetMayUseIme =
                 WindowManager.LayoutParams.mayUseInputMethod(imeLayeringTarget.mAttrs.flags)
                         || imeLayeringTarget.mAttrs.type == TYPE_APPLICATION_STARTING;
-        if (isImeTargetMismatchOnEmbedding(imeLayeringTarget, imeInputTarget)) {
-            return true;
-        }
         // Do not change parent if the window hasn't requested IME.
         var inputAndLayeringTargetsDisagree = (imeInputTarget == null
                 || imeLayeringTarget.mActivityRecord != imeInputTarget.getActivityRecord());
@@ -83,26 +83,45 @@
         return !inputTargetStale;
     }
 
-    private static boolean isImeTargetMismatchOnEmbedding(
+
+    /**
+     * Called from {@link DisplayContent#computeImeParent()} to check the given IME targets if the
+     * IME surface parent should be updated in ActivityEmbeddings.
+     *
+     * As the IME layering target is calculated according to the window hierarchy by
+     * {@link DisplayContent#computeImeTarget}, the layering target and input target may be
+     * different when the window hasn't started input connection, WindowManagerService hasn't yet
+     * received the input target which reported from InputMethodManagerService. To make the IME
+     * surface will be shown on the best fit IME layering target, we basically won't update IME
+     * parent until both IME input and layering target updated for better IME transition.
+     *
+     * However, in activity embedding, tapping a window won't update it to the top window so the
+     * calculated IME layering target may higher than input target. Update IME parent for this case.
+     *
+     * @return {@code true} means the layer of IME layering target is higher than the input target
+     * and {@link DisplayContent#computeImeParent()} should keep progressing to update the IME
+     * surface parent on the display in case the IME surface left behind.
+     */
+    private static boolean shouldComputeImeParentForEmbeddedActivity(
             @Nullable WindowState imeLayeringTarget, @Nullable InputTarget imeInputTarget) {
         if (imeInputTarget == null || imeLayeringTarget == null) {
             return false;
         }
-        final ActivityRecord inputTargetRecord = imeInputTarget.getActivityRecord();
-        final ActivityRecord layeringTargetRecord = imeLayeringTarget.getActivityRecord();
         final WindowState inputTargetWindow = imeInputTarget.getWindowState();
-        if (inputTargetRecord == null || layeringTargetRecord == null
-                || inputTargetWindow == null) {
+        if (inputTargetWindow == null || !imeLayeringTarget.isAttached()
+                || !inputTargetWindow.isAttached()) {
             return false;
         }
-        final boolean isImeTargetEmbedded = inputTargetRecord.isEmbedded()
-                && layeringTargetRecord.isEmbedded();
-        // The IME layering target is calculated by the window hierarchy in DisplayContent.
-        // The layering target and input target may be different when the window hasn't started
-        // input connection, WMS hasn't received the target which reported from IMMS. We basically
-        // won't update IME parent for better IME transition.
-        // But in activity embedding, tapping a window won't update it to the top window so the IME
-        // layering target may higher than input target. Update IME parent for this case.
-        return isImeTargetEmbedded && imeLayeringTarget.compareTo(inputTargetWindow) > 0;
+
+        final ActivityRecord inputTargetRecord = imeInputTarget.getActivityRecord();
+        final ActivityRecord layeringTargetRecord = imeLayeringTarget.getActivityRecord();
+        if (inputTargetRecord == null || layeringTargetRecord == null
+                || inputTargetRecord == layeringTargetRecord
+                || (inputTargetRecord.getTask() != layeringTargetRecord.getTask())
+                || !inputTargetRecord.isEmbedded() || !layeringTargetRecord.isEmbedded()) {
+            // Check whether the input target and layering target are embedded in the same Task.
+            return false;
+        }
+        return imeLayeringTarget.compareTo(inputTargetWindow) > 0;
     }
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index e1dbe01a..dc674c2 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -97,6 +97,7 @@
 import android.view.RoundedCorner;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 
 import com.android.internal.R;
@@ -131,12 +132,6 @@
 
     private final ActivityRecord mActivityRecord;
 
-    /**
-     * Taskbar expanded height. Used to determine when to crop an app window to display the
-     * rounded corners above the expanded taskbar.
-     */
-    private final float mExpandedTaskBarHeight;
-
     // TODO(b/265576778): Cache other overrides as well.
 
     // Corresponds to OVERRIDE_ANY_ORIENTATION
@@ -253,9 +248,6 @@
                                 /* checkDeviceConfig */ true),
                         PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
 
-        mExpandedTaskBarHeight =
-                getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
-
         mBooleanPropertyAllowOrientationOverride =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
                         /* gatingCondition */ null,
@@ -1138,11 +1130,13 @@
     @VisibleForTesting
     @Nullable
     InsetsSource getExpandedTaskbarOrNull(final WindowState mainWindow) {
-        final InsetsSource taskbar = mainWindow.getInsetsState().peekSource(
-                InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
-        if (taskbar != null && taskbar.isVisible()
-                && taskbar.getFrame().height() >= mExpandedTaskBarHeight) {
-            return taskbar;
+        final InsetsState state = mainWindow.getInsetsState();
+        for (int i = state.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = state.sourceAt(i);
+            if (source.getType() == WindowInsets.Type.navigationBars()
+                    && source.insetsRoundedCornerFrame() && source.isVisible()) {
+                return source;
+            }
         }
         return null;
     }
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 4f506a5..07f3bc6 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -14,6 +14,7 @@
 tigerhuang@google.com
 lihongyu@google.com
 mariiasand@google.com
+rgl@google.com
 
 per-file BackgroundActivityStartController.java = set noparent
 per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com, rickywai@google.com
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 2af5460..cc81d52 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2825,19 +2825,23 @@
      * Account for specified insets to crop the animation bounds by to avoid the animation
      * occurring over "out of bounds" regions
      *
-     * For example this is used to make sure the tasks are cropped to be fully above the
+     * For example this is used to make sure the tasks are cropped to be fully above the expanded
      * taskbar when animating.
      *
-     * @param animationBounds The animations bounds to adjust to account for the custom spec insets.
+     * TEMPORARY FIELD (b/202383002)
+     * TODO: Remove once we use surfaceflinger rounded corners on tasks rather than taskbar overlays
+     *       or when shell transitions are fully enabled
+     *
+     * @param animationBounds The animation bounds to adjust to account for the custom spec insets.
      */
     void adjustAnimationBoundsForTransition(Rect animationBounds) {
         TaskTransitionSpec spec = mWmService.mTaskTransitionSpec;
         if (spec != null) {
             final InsetsState state =
                     getDisplayContent().getInsetsStateController().getRawInsetsState();
-            for (int id : spec.animationBoundInsets) {
-                final InsetsSource source = state.peekSource(id);
-                if (source != null) {
+            for (int i = state.sourceSize() - 1; i >= 0; i--) {
+                final InsetsSource source = state.sourceAt(i);
+                if (source.insetsRoundedCornerFrame()) {
                     animationBounds.inset(source.calculateVisibleInsets(animationBounds));
                 }
             }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 8cbd553..3cec3aa 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1225,7 +1225,7 @@
 
         // Clear last paused activity if focused root task changed while sleeping, so that the
         // top activity of current focused task can be resumed.
-        if (mDisplayContent.isSleeping()) {
+        if (mDisplayContent.isSleeping() && currentFocusedTask != null) {
             currentFocusedTask.clearLastPausedActivity();
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c2afeaf..808e79d 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1933,7 +1933,7 @@
                         1f);
                 mBackScreenshots.put(r.mActivityComponent.flattenToString(), backBuffer);
             }
-            child.asActivityRecord().inHistory = true;
+            addingActivity.inHistory = true;
             task.onDescendantActivityAdded(taskHadActivity, activityType, addingActivity);
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bb706ec..da7400c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -123,7 +123,6 @@
 import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -3193,8 +3192,7 @@
                 animationRunnerBuilder.setTaskBackgroundColor(getTaskAnimationBackgroundColor());
                 // TODO: Remove when we migrate to shell (b/202383002)
                 if (mWmService.mTaskTransitionSpec != null) {
-                    animationRunnerBuilder.hideInsetSourceViewOverflows(
-                            mWmService.mTaskTransitionSpec.animationBoundInsets);
+                    animationRunnerBuilder.hideInsetSourceViewOverflows();
                 }
             }
 
@@ -4115,11 +4113,12 @@
             }
         }
 
-        private void hideInsetSourceViewOverflows(Set<Integer> sourceIds) {
-            final InsetsStateController controller = getDisplayContent().getInsetsStateController();
-            for (int id : sourceIds) {
-                final InsetsSourceProvider insetProvider = controller.peekSourceProvider(id);
-                if (insetProvider == null) {
+        private void hideInsetSourceViewOverflows() {
+            final SparseArray<WindowContainerInsetsSourceProvider> providers =
+                    getDisplayContent().getInsetsStateController().getSourceProviders();
+            for (int i = providers.size(); i >= 0; i--) {
+                final InsetsSourceProvider insetProvider = providers.valueAt(i);
+                if (!insetProvider.getSource().insetsRoundedCornerFrame()) {
                     return;
                 }
 
diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
index 3d504ef..9c50a5a 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
@@ -49,11 +49,14 @@
     /** Represents the results of a given query into the registry. */
     public static final class FilterResult {
         final String mPackageName;
+        final String mFlattenedRequest;
         final List<CredentialEntry> mCredentialEntries;
 
         private FilterResult(String packageName,
+                String flattenedRequest,
                 List<CredentialEntry> credentialEntries) {
             mPackageName = packageName;
+            mFlattenedRequest = flattenedRequest;
             mCredentialEntries = credentialEntries;
         }
     }
@@ -133,12 +136,13 @@
     /** Returns package names and entries of a CredentialProviders that can satisfy a given
      * {@link CredentialDescription}. */
     public Set<FilterResult> getFilteredResultForProvider(String packageName,
-            List<String> flatRequestStrings) {
+            String flatRequestStrings) {
         Set<FilterResult> result = new HashSet<>();
         Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
         for (CredentialDescription containedDescription: currentSet) {
-            if (flatRequestStrings.contains(containedDescription.getFlattenedRequestString())) {
-                result.add(new FilterResult(packageName, containedDescription
+            if (flatRequestStrings.equals(containedDescription.getFlattenedRequestString())) {
+                result.add(new FilterResult(packageName,
+                        containedDescription.getFlattenedRequestString(), containedDescription
                         .getCredentialEntries()));
             }
         }
@@ -147,13 +151,15 @@
 
     /** Returns package names of CredentialProviders that can satisfy a given
      * {@link CredentialDescription}. */
-    public Set<String> getMatchingProviders(Set<String> flatRequestString) {
-        Set<String> result = new HashSet<>();
+    public Set<FilterResult> getMatchingProviders(Set<String> flatRequestString) {
+        Set<FilterResult> result = new HashSet<>();
         for (String packageName: mCredentialDescriptions.keySet()) {
             Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
             for (CredentialDescription containedDescription : currentSet) {
                 if (flatRequestString.contains(containedDescription.getFlattenedRequestString())) {
-                    result.add(packageName);
+                    result.add(new FilterResult(packageName,
+                            containedDescription.getFlattenedRequestString(), containedDescription
+                            .getCredentialEntries()));
                 }
             }
         }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index cce12a2..7a4e7df 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.credentials;
 
 import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
+import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR;
 import static android.content.Context.CREDENTIAL_SERVICE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
@@ -58,6 +59,7 @@
 import android.service.credentials.CredentialProviderInfo;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -66,6 +68,7 @@
 import com.android.server.infra.SecureSettingsServiceNameResolver;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -274,24 +277,25 @@
     // to be guarded by 'service.mLock', which is the same as mLock.
     private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
             GetRequestSession session,
-            List<String> requestOptions,
-            Set<String> activeCredentialContainers) {
+            Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
+                    activeCredentialContainers) {
         List<ProviderSession> providerSessions = new ArrayList<>();
-        // Invoke all services of a user to initiate a provider session
-        for (String packageName : activeCredentialContainers) {
+        for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result :
+                activeCredentialContainers) {
             providerSessions.add(
                     ProviderRegistryGetSession.createNewSession(
                             mContext,
                             UserHandle.getCallingUserId(),
                             session,
-                            packageName,
-                            requestOptions));
+                            result.second.mPackageName,
+                            result.first));
         }
         return providerSessions;
     }
 
     @NonNull
-    private Set<String> getFilteredResultFromRegistry(List<CredentialOption> options) {
+    private Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
+            getFilteredResultFromRegistry(List<CredentialOption> options) {
         // Session for active/provisioned credential descriptions;
         CredentialDescriptionRegistry registry =
                 CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
@@ -307,7 +311,22 @@
                         .collect(Collectors.toSet());
 
         // All requested credential descriptions based on the given request.
-        return registry.getMatchingProviders(requestedCredentialDescriptions);
+        Set<CredentialDescriptionRegistry.FilterResult> filterResults =
+                registry.getMatchingProviders(requestedCredentialDescriptions);
+
+        Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>> result =
+                new HashSet<>();
+
+        for (CredentialDescriptionRegistry.FilterResult filterResult: filterResults) {
+            for (CredentialOption credentialOption: options) {
+                if (filterResult.mFlattenedRequest.equals(credentialOption
+                        .getCredentialRetrievalData()
+                        .getString(CredentialOption.FLATTENED_REQUEST))) {
+                    result.add(new Pair<>(credentialOption, filterResult));
+                }
+            }
+        }
+        return result;
     }
 
     @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
@@ -342,21 +361,22 @@
             int userId,
             @Nullable String origin) {
         final PackageInfo packageInfo;
-        String actualPackageName = origin == null ? realPackageName : origin;
+        CallingAppInfo callingAppInfo;
         try {
             packageInfo =
                 getContext()
                     .getPackageManager()
                     .getPackageInfoAsUser(
-                        actualPackageName,
+                            realPackageName,
                         PackageManager.PackageInfoFlags.of(
                             PackageManager.GET_SIGNING_CERTIFICATES),
                         userId);
+            callingAppInfo = new CallingAppInfo(realPackageName, packageInfo.signingInfo, origin);
         } catch (PackageManager.NameNotFoundException e) {
             Log.i(TAG, "Issue while retrieving signatureInfo : " + e.getMessage());
-            return new CallingAppInfo(actualPackageName, null);
+            callingAppInfo = new CallingAppInfo(realPackageName, null, origin);
         }
-        return new CallingAppInfo(actualPackageName, packageInfo.signingInfo);
+        return callingAppInfo;
     }
 
     final class CredentialManagerServiceStub extends ICredentialManager.Stub {
@@ -368,9 +388,15 @@
             Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
 
+            if (request.getOrigin() != null) {
+                // Check privileged permissions
+                mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
+            }
+
             final int userId = UserHandle.getCallingUserId();
             final int callingUid = Binder.getCallingUid();
             enforceCallingPackage(callingPackage, callingUid);
+
             // New request session, scoped for this request only.
             final GetRequestSession session =
                     new GetRequestSession(
@@ -379,51 +405,20 @@
                             callingUid,
                             callback,
                             request,
-                            constructCallingAppInfo(callingPackage, userId, null),
+                            constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
                             CancellationSignal.fromTransport(cancelTransport));
 
             processGetCredential(request, callback, session);
             return cancelTransport;
         }
 
-        public ICancellationSignal executeGetCredentialWithOrigin(
-                GetCredentialRequest request,
-                IGetCredentialCallback callback,
-                final String callingPackage,
-                final String origin) {
-            Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
-            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
-
-            // Check privileged permissions
-            mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
-
-            final int userId = UserHandle.getCallingUserId();
-            final int callingUid = Binder.getCallingUid();
-            enforceCallingPackage(callingPackage, callingUid);
-
-            // New request session, scoped for this request only.
-            final GetRequestSession session =
-                    new GetRequestSession(
-                        getContext(),
-                        userId,
-                        callingUid,
-                        callback,
-                        request,
-                        constructCallingAppInfo(callingPackage, userId, origin),
-                        CancellationSignal.fromTransport(cancelTransport));
-
-            processGetCredential(request, callback, session);
-            return cancelTransport;
-        }
-
         private void processGetCredential(
                 GetCredentialRequest request,
                 IGetCredentialCallback callback,
                 GetRequestSession session) {
             List<ProviderSession> providerSessions;
 
-            // TODO(b/268143699): temporarily disable the flag due to bug.
-            if (false) {
+            if (isCredentialDescriptionApiEnabled()) {
                 List<CredentialOption> optionsThatRequireActiveCredentials =
                         request.getCredentialOptions().stream()
                         .filter(
@@ -453,15 +448,6 @@
                 List<ProviderSession> sessionsWithoutRemoteService =
                         initiateProviderSessionsWithActiveContainers(
                         session,
-                        optionsThatRequireActiveCredentials.stream()
-                            .map(
-                                getCredentialOption ->
-                                    getCredentialOption
-                                        .getCredentialRetrievalData()
-                                        .getString(
-                                            CredentialOption
-                                                .FLATTENED_REQUEST))
-                            .collect(Collectors.toList()),
                         getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));
 
                 List<ProviderSession> sessionsWithRemoteService =
@@ -511,6 +497,11 @@
                     + callingPackage);
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
 
+            if (request.getOrigin() != null) {
+                // Check privileged permissions
+                mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
+            }
+
             final int userId = UserHandle.getCallingUserId();
             final int callingUid = Binder.getCallingUid();
             enforceCallingPackage(callingPackage, callingUid);
@@ -523,43 +514,13 @@
                             callingUid,
                             request,
                             callback,
-                            constructCallingAppInfo(callingPackage, userId, null),
+                            constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
                             CancellationSignal.fromTransport(cancelTransport));
 
             processCreateCredential(request, callback, session);
             return cancelTransport;
         }
 
-        public ICancellationSignal executeCreateCredentialWithOrigin(
-                CreateCredentialRequest request,
-                ICreateCredentialCallback callback,
-                String callingPackage,
-                String origin) {
-            Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
-            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
-
-            // Check privileged permissions
-            mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
-
-            final int userId = UserHandle.getCallingUserId();
-            final int callingUid = Binder.getCallingUid();
-            enforceCallingPackage(callingPackage, callingUid);
-
-            // New request session, scoped for this request only.
-            final CreateRequestSession session =
-                    new CreateRequestSession(
-                        getContext(),
-                        userId,
-                        callingUid,
-                        request,
-                        callback,
-                        constructCallingAppInfo(callingPackage, userId, origin),
-                        CancellationSignal.fromTransport(cancelTransport));
-
-            processCreateCredential(request, callback, session);
-            return cancelTransport;
-        }
-
         private void processCreateCredential(
                 CreateCredentialRequest request,
                 ICreateCredentialCallback callback,
@@ -671,7 +632,8 @@
             }
 
             // Send an intent to the UI that we have new enabled providers.
-            getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent());
+            getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent(),
+                    LAUNCH_CREDENTIAL_SELECTOR);
         }
 
         @Override
@@ -776,6 +738,10 @@
                 throws IllegalArgumentException, NonCredentialProviderCallerException {
             Log.i(TAG, "registerCredentialDescription");
 
+            if (!isCredentialDescriptionApiEnabled()) {
+                throw new UnsupportedOperationException();
+            }
+
             enforceCallingPackage(callingPackage, Binder.getCallingUid());
 
             List<CredentialProviderInfo> services =
@@ -828,6 +794,10 @@
                 throws IllegalArgumentException {
             Log.i(TAG, "registerCredentialDescription");
 
+            if (!isCredentialDescriptionApiEnabled()) {
+                throw new UnsupportedOperationException();
+            }
+
             enforceCallingPackage(callingPackage, Binder.getCallingUid());
 
             List<CredentialProviderInfo> services =
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 5af8080..a57cb5f 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -23,7 +23,6 @@
 import android.content.Intent;
 import android.credentials.CredentialOption;
 import android.credentials.GetCredentialException;
-import android.credentials.GetCredentialRequest;
 import android.credentials.GetCredentialResponse;
 import android.credentials.ui.Entry;
 import android.credentials.ui.GetCredentialProviderData;
@@ -49,7 +48,7 @@
  *
  * @hide
  */
-public class ProviderRegistryGetSession extends ProviderSession<GetCredentialRequest,
+public class ProviderRegistryGetSession extends ProviderSession<CredentialOption,
         Set<CredentialDescriptionRegistry.FilterResult>> {
 
     private static final String TAG = "ProviderRegistryGetSession";
@@ -62,15 +61,14 @@
             @UserIdInt int userId,
             @NonNull GetRequestSession getRequestSession,
             @NonNull String credentialProviderPackageName,
-            @NonNull List<String> requestOptions) {
+            @NonNull CredentialOption requestOption) {
         return new ProviderRegistryGetSession(
                 context,
                 userId,
                 getRequestSession,
-                getRequestSession.mClientRequest,
                 getRequestSession.mClientAppInfo,
                 credentialProviderPackageName,
-                requestOptions);
+                requestOption);
     }
 
     @NonNull
@@ -82,24 +80,22 @@
     @NonNull
     private final String mCredentialProviderPackageName;
     @NonNull
-    private final GetRequestSession mGetRequestSession;
-    @NonNull
-    private final List<String> mRequestOptions;
+    private final String mFlattenedRequestOptionString;
     private List<CredentialEntry> mCredentialEntries;
 
     protected ProviderRegistryGetSession(@NonNull Context context,
             @NonNull int userId,
             @NonNull GetRequestSession session,
-            @NonNull GetCredentialRequest request,
             @NonNull CallingAppInfo callingAppInfo,
             @NonNull String servicePackageName,
-            @NonNull List<String> requestOptions) {
-        super(context, null, request, session, userId, null);
-        mGetRequestSession = session;
+            @NonNull CredentialOption requestOption) {
+        super(context, null, requestOption, session, userId, null);
         mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
         mCallingAppInfo = callingAppInfo;
         mCredentialProviderPackageName = servicePackageName;
-        mRequestOptions = requestOptions;
+        mFlattenedRequestOptionString = requestOption
+                .getCredentialRetrievalData()
+                .getString(CredentialOption.FLATTENED_REQUEST);
     }
 
     private List<Entry> prepareUiCredentialEntries(
@@ -114,23 +110,18 @@
             Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
             credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
                     credentialEntry.getSlice(),
-                    setUpFillInIntent(credentialEntry.getType())));
+                    setUpFillInIntent()));
         }
         return credentialUiEntries;
     }
 
-    private Intent setUpFillInIntent(String type) {
+    private Intent setUpFillInIntent() {
         Intent intent = new Intent();
-        for (CredentialOption option : mProviderRequest.getCredentialOptions()) {
-            if (option.getType().equals(type)) {
-                intent.putExtra(
-                        CredentialProviderService
-                                .EXTRA_GET_CREDENTIAL_REQUEST,
-                        new android.service.credentials.GetCredentialRequest(
-                                mCallingAppInfo, option));
-                return intent;
-            }
-        }
+        intent.putExtra(
+                CredentialProviderService
+                        .EXTRA_GET_CREDENTIAL_REQUEST,
+                new android.service.credentials.GetCredentialRequest(
+                        mCallingAppInfo, mProviderRequest));
         return intent;
     }
 
@@ -176,11 +167,6 @@
 
     private void onCredentialEntrySelected(CredentialEntry credentialEntry,
             ProviderPendingIntentResponse providerPendingIntentResponse) {
-        if (!mCredentialEntries.contains(credentialEntry)) {
-            invokeCallbackWithError("",
-                    "");
-        }
-
         if (providerPendingIntentResponse != null) {
             // Check if pending intent has an error
             GetCredentialException exception = maybeGetPendingIntentException(
@@ -228,13 +214,13 @@
     protected void invokeSession() {
         mProviderResponse = mCredentialDescriptionRegistry
                 .getFilteredResultForProvider(mCredentialProviderPackageName,
-                        mRequestOptions);
+                        mFlattenedRequestOptionString);
         mCredentialEntries = mProviderResponse.stream().flatMap(
                         (Function<CredentialDescriptionRegistry.FilterResult,
                                 Stream<CredentialEntry>>) filterResult
                         -> filterResult.mCredentialEntries.stream())
                 .collect(Collectors.toList());
-        setStatus(Status.CREDENTIALS_RECEIVED);
+        updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
         // TODO(use metric later)
     }
 
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 0ca4dfc..17e1744 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -113,6 +113,7 @@
     private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;
     @VisibleForTesting
     static final int MAX_CACHED_RECENT_SHORTCUTS = 30;
+    private static final int DEBOUNCE_LENGTH_MS = 500;
 
     private final Context mContext;
     private final Injector mInjector;
@@ -129,6 +130,7 @@
     private final List<PeopleService.ConversationsListener> mConversationsListeners =
             new ArrayList<>(1);
     private final Handler mHandler;
+    private final PerPackageThrottler mShortcutsThrottler;
 
     private ContentObserver mCallLogContentObserver;
     private ContentObserver mMmsSmsContentObserver;
@@ -140,14 +142,17 @@
     private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver;
 
     public DataManager(Context context) {
-        this(context, new Injector(), BackgroundThread.get().getLooper());
+        this(context, new Injector(), BackgroundThread.get().getLooper(),
+                new PerPackageThrottlerImpl(BackgroundThread.getHandler(), DEBOUNCE_LENGTH_MS));
     }
 
-    DataManager(Context context, Injector injector, Looper looper) {
+    DataManager(Context context, Injector injector, Looper looper,
+            PerPackageThrottler shortcutsThrottler) {
         mContext = context;
         mInjector = injector;
         mScheduledExecutor = mInjector.createScheduledExecutor();
         mHandler = new Handler(looper);
+        mShortcutsThrottler = shortcutsThrottler;
     }
 
     /** Initialization. Called when the system services are up running. */
@@ -851,12 +856,12 @@
         // pair of <package name, conversation info>
         List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>();
         userData.forAllPackages(packageData -> {
-                packageData.forAllConversations(conversationInfo -> {
-                    if (isEligibleForCleanUp(conversationInfo)) {
-                        cachedConvos.add(
-                                Pair.create(packageData.getPackageName(), conversationInfo));
-                    }
-                });
+            packageData.forAllConversations(conversationInfo -> {
+                if (isEligibleForCleanUp(conversationInfo)) {
+                    cachedConvos.add(
+                            Pair.create(packageData.getPackageName(), conversationInfo));
+                }
+            });
         });
         if (cachedConvos.size() <= targetCachedCount) {
             return;
@@ -867,8 +872,8 @@
                 numToUncache + 1,
                 Comparator.comparingLong((Pair<String, ConversationInfo> pair) ->
                         Math.max(
-                            pair.second.getLastEventTimestamp(),
-                            pair.second.getCreationTimestamp())).reversed());
+                                pair.second.getLastEventTimestamp(),
+                                pair.second.getCreationTimestamp())).reversed());
         for (Pair<String, ConversationInfo> cached : cachedConvos) {
             if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) {
                 continue;
@@ -1104,26 +1109,35 @@
         @Override
         public void onShortcutsAddedOrUpdated(@NonNull String packageName,
                 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
-            mInjector.getBackgroundExecutor().execute(() -> {
-                PackageData packageData = getPackage(packageName, user.getIdentifier());
-                for (ShortcutInfo shortcut : shortcuts) {
-                    if (ShortcutHelper.isConversationShortcut(
-                            shortcut, mShortcutServiceInternal, user.getIdentifier())) {
-                        if (shortcut.isCached()) {
-                            ConversationInfo conversationInfo = packageData != null
-                                    ? packageData.getConversationInfo(shortcut.getId()) : null;
-                            if (conversationInfo == null
-                                    || !conversationInfo.isShortcutCachedForNotification()) {
-                                // This is a newly cached shortcut. Clean up the existing cached
-                                // shortcuts to ensure the cache size is under the limit.
-                                cleanupCachedShortcuts(user.getIdentifier(),
-                                        MAX_CACHED_RECENT_SHORTCUTS - 1);
+            mShortcutsThrottler.scheduleDebounced(
+                    new Pair<>(packageName, user.getIdentifier()),
+                    () -> {
+                        PackageData packageData = getPackage(packageName, user.getIdentifier());
+                        List<ShortcutInfo> queriedShortcuts = getShortcuts(packageName,
+                                user.getIdentifier(), null);
+                        boolean hasCachedShortcut = false;
+                        for (ShortcutInfo shortcut : queriedShortcuts) {
+                            if (ShortcutHelper.isConversationShortcut(
+                                    shortcut, mShortcutServiceInternal, user.getIdentifier())) {
+                                if (shortcut.isCached()) {
+                                    ConversationInfo info = packageData != null
+                                            ? packageData.getConversationInfo(shortcut.getId())
+                                            : null;
+                                    if (info == null
+                                            || !info.isShortcutCachedForNotification()) {
+                                        hasCachedShortcut = true;
+                                    }
+                                }
+                                addOrUpdateConversationInfo(shortcut);
                             }
                         }
-                        addOrUpdateConversationInfo(shortcut);
-                    }
-                }
-            });
+                        // Added at least one new conversation. Uncache older existing cached
+                        // shortcuts to ensure the cache size is under the limit.
+                        if (hasCachedShortcut) {
+                            cleanupCachedShortcuts(user.getIdentifier(),
+                                    MAX_CACHED_RECENT_SHORTCUTS);
+                        }
+                    });
         }
 
         @Override
diff --git a/services/people/java/com/android/server/people/data/PerPackageThrottler.java b/services/people/java/com/android/server/people/data/PerPackageThrottler.java
new file mode 100644
index 0000000..3d6cd84
--- /dev/null
+++ b/services/people/java/com/android/server/people/data/PerPackageThrottler.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people.data;
+
+import android.util.Pair;
+
+/** The interface for throttling expensive runnables per package. */
+interface PerPackageThrottler {
+    /**
+     * Schedule a runnable to run in the future, and debounce runnables for same {@code pkgUserId}
+     * that occur until that future has run.
+     */
+    void scheduleDebounced(Pair<String, Integer> pkgUserId, Runnable runnable);
+}
diff --git a/services/people/java/com/android/server/people/data/PerPackageThrottlerImpl.java b/services/people/java/com/android/server/people/data/PerPackageThrottlerImpl.java
new file mode 100644
index 0000000..fa5a67b
--- /dev/null
+++ b/services/people/java/com/android/server/people/data/PerPackageThrottlerImpl.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people.data;
+
+import android.os.Handler;
+import android.util.Pair;
+
+import java.util.HashSet;
+
+/**
+ * A class that implements a per-package throttler that prevents a runnable from executing more than
+ * once every {@code debounceTime}.
+ */
+public class PerPackageThrottlerImpl implements PerPackageThrottler {
+    private final Handler mBackgroundHandler;
+    private final int mDebounceTime;
+    private final HashSet<Pair<String, Integer>> mPkgScheduledTasks = new HashSet<>();
+
+    PerPackageThrottlerImpl(Handler backgroundHandler, int debounceTime) {
+        mBackgroundHandler = backgroundHandler;
+        mDebounceTime = debounceTime;
+    }
+
+    @Override
+    public synchronized void scheduleDebounced(
+            Pair<String, Integer> pkgUserId, Runnable runnable) {
+        if (mPkgScheduledTasks.contains(pkgUserId)) {
+            return;
+        }
+        mPkgScheduledTasks.add(pkgUserId);
+        mBackgroundHandler.postDelayed(() -> {
+            synchronized (this) {
+                mPkgScheduledTasks.remove(pkgUserId);
+                runnable.run();
+            }
+        }, mDebounceTime);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExpectableTestCase.java b/services/tests/mockingservicestests/src/com/android/server/ExpectableTestCase.java
new file mode 100644
index 0000000..a329f5a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/ExpectableTestCase.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import com.google.common.annotations.GwtIncompatible;
+import com.google.common.base.Optional;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.Table;
+import com.google.common.truth.BigDecimalSubject;
+import com.google.common.truth.BooleanSubject;
+import com.google.common.truth.ClassSubject;
+import com.google.common.truth.ComparableSubject;
+import com.google.common.truth.DoubleSubject;
+import com.google.common.truth.Expect;
+import com.google.common.truth.FloatSubject;
+import com.google.common.truth.GuavaOptionalSubject;
+import com.google.common.truth.IntegerSubject;
+import com.google.common.truth.IterableSubject;
+import com.google.common.truth.LongSubject;
+import com.google.common.truth.MapSubject;
+import com.google.common.truth.MultimapSubject;
+import com.google.common.truth.MultisetSubject;
+import com.google.common.truth.ObjectArraySubject;
+import com.google.common.truth.PrimitiveBooleanArraySubject;
+import com.google.common.truth.PrimitiveByteArraySubject;
+import com.google.common.truth.PrimitiveCharArraySubject;
+import com.google.common.truth.PrimitiveDoubleArraySubject;
+import com.google.common.truth.PrimitiveFloatArraySubject;
+import com.google.common.truth.PrimitiveIntArraySubject;
+import com.google.common.truth.PrimitiveLongArraySubject;
+import com.google.common.truth.PrimitiveShortArraySubject;
+import com.google.common.truth.StandardSubjectBuilder;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Subject;
+import com.google.common.truth.TableSubject;
+import com.google.common.truth.ThrowableSubject;
+
+import org.junit.Rule;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+// NOTE: it could be a more generic AbstractTruthTestCase that provide similar methods
+// for assertThat() / assertWithMessage(), but then we'd need to remove all static import imports
+// from classes that indirectly extend it.
+/**
+ * Base class to make it easier to use {@code Truth} {@link Expect} assertions.
+ */
+public abstract class ExpectableTestCase {
+
+    @Rule
+    public final Expect mExpect = Expect.create();
+
+    protected final StandardSubjectBuilder expectWithMessage(String msg) {
+        return mExpect.withMessage(msg);
+    }
+
+    protected final StandardSubjectBuilder expectWithMessage(String format, Object...args) {
+        return mExpect.withMessage(format, args);
+    }
+
+    protected final <ComparableT extends Comparable<?>> ComparableSubject<ComparableT> expectThat(
+            ComparableT actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final BigDecimalSubject expectThat(BigDecimal actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final Subject expectThat(Object actual) {
+        return mExpect.that(actual);
+    }
+
+    @GwtIncompatible("ClassSubject.java")
+    protected final ClassSubject expectThat(Class<?> actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final ThrowableSubject expectThat(Throwable actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final LongSubject expectThat(Long actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final DoubleSubject expectThat(Double actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final FloatSubject expectThat(Float actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final IntegerSubject expectThat(Integer actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final BooleanSubject expectThat(Boolean actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final StringSubject expectThat(String actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final IterableSubject expectThat(Iterable<?> actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final <T> ObjectArraySubject<T> expectThat(T[] actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final PrimitiveBooleanArraySubject expectThat(boolean[] actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final PrimitiveShortArraySubject expectThat(short[] actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final PrimitiveIntArraySubject expectThat(int[] actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final PrimitiveLongArraySubject expectThat(long[] actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final PrimitiveCharArraySubject expectThat(char[] actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final PrimitiveByteArraySubject expectThat(byte[] actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final PrimitiveFloatArraySubject expectThat(float[] actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final PrimitiveDoubleArraySubject expectThat(double[] actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final GuavaOptionalSubject expectThat(Optional<?> actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final MapSubject expectThat(Map<?, ?> actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final MultimapSubject expectThat(Multimap<?, ?> actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final MultisetSubject expectThat(Multiset<?> actual) {
+        return mExpect.that(actual);
+    }
+
+    protected final TableSubject expectThat(Table<?, ?, ?> actual) {
+        return mExpect.that(actual);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java
new file mode 100644
index 0000000..881dd50
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.internal.util.Preconditions;
+import com.android.modules.utils.testing.StaticMockFixture;
+import com.android.modules.utils.testing.StaticMockFixtureRule;
+
+import org.mockito.Mockito;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Rule to make it easier to use Extended Mockito.
+ *
+ * <p>It's derived from {@link StaticMockFixtureRule}, with the additional features:
+ *
+ * <ul>
+ *   <li>Easier to define which classes must be statically mocked or spied
+ *   <li>Automatically starts mocking (so tests don't need a mockito runner or rule)
+ *   <li>Automatically clears the inlined mocks at the end (to avoid OOM)
+ *   <li>Allows other customization like strictness
+ * </ul>
+ */
+public final class ExtendedMockitoRule extends StaticMockFixtureRule {
+
+    private static final String TAG = ExtendedMockitoRule.class.getSimpleName();
+
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private final Object mTestClassInstance;
+    private final Strictness mStrictness;
+
+    private ExtendedMockitoRule(Builder builder) {
+        super(() -> new SimpleStatickMockFixture(builder.mMockedStaticClasses,
+                builder.mSpiedStaticClasses, builder.mDynamicSessionBuilderConfigurator,
+                builder.mAfterSessionFinishedCallback));
+        mTestClassInstance = builder.mTestClassInstance;
+        mStrictness = builder.mStrictness;
+        if (VERBOSE) {
+            Log.v(TAG, "strictness=" + mStrictness + ", testClassInstance" + mTestClassInstance
+                    + ", mockedStaticClasses=" + builder.mMockedStaticClasses
+                    + ", spiedStaticClasses=" + builder.mSpiedStaticClasses
+                    + ", dynamicSessionBuilderConfigurator="
+                    + builder.mDynamicSessionBuilderConfigurator
+                    + ", afterSessionFinishedCallback=" + builder.mAfterSessionFinishedCallback);
+        }
+    }
+
+    @Override
+    public StaticMockitoSessionBuilder getSessionBuilder() {
+        StaticMockitoSessionBuilder sessionBuilder = super.getSessionBuilder();
+        if (mStrictness != null) {
+            if (VERBOSE) {
+                Log.v(TAG, "Setting strictness to " + mStrictness + " on " + sessionBuilder);
+            }
+            sessionBuilder.strictness(mStrictness);
+        }
+        return sessionBuilder.initMocks(mTestClassInstance);
+    }
+
+    public static final class Builder {
+        private final Object mTestClassInstance;
+        private @Nullable Strictness mStrictness;
+        private final List<Class<?>> mMockedStaticClasses = new ArrayList<>();
+        private final List<Class<?>> mSpiedStaticClasses = new ArrayList<>();
+        private @Nullable Visitor<StaticMockitoSessionBuilder> mDynamicSessionBuilderConfigurator;
+        private @Nullable Runnable mAfterSessionFinishedCallback;
+
+        public Builder(Object testClassInstance) {
+            mTestClassInstance = Objects.requireNonNull(testClassInstance);
+        }
+
+        public Builder setStrictness(Strictness strictness) {
+            mStrictness = Objects.requireNonNull(strictness);
+            return this;
+        }
+
+        public Builder mockStatic(Class<?> clazz) {
+            Objects.requireNonNull(clazz);
+            Preconditions.checkState(!mMockedStaticClasses.contains(clazz),
+                    "class %s already mocked", clazz);
+            mMockedStaticClasses.add(clazz);
+            return this;
+        }
+
+        public Builder spyStatic(Class<?> clazz) {
+            Objects.requireNonNull(clazz);
+            Preconditions.checkState(!mSpiedStaticClasses.contains(clazz),
+                    "class %s already spied", clazz);
+            mSpiedStaticClasses.add(clazz);
+            return this;
+        }
+
+        public Builder dynamiclyConfigureSessionBuilder(
+                Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator) {
+            mDynamicSessionBuilderConfigurator = Objects
+                    .requireNonNull(dynamicSessionBuilderConfigurator);
+            return this;
+        }
+
+        public Builder afterSessionFinished(Runnable runnable) {
+            mAfterSessionFinishedCallback = Objects.requireNonNull(runnable);
+            return this;
+        }
+
+        public ExtendedMockitoRule build() {
+            return new ExtendedMockitoRule(this);
+        }
+    }
+
+    private static final class SimpleStatickMockFixture implements StaticMockFixture {
+
+        private final List<Class<?>> mMockedStaticClasses;
+        private final List<Class<?>> mSpiedStaticClasses;
+        @Nullable
+        private final Visitor<StaticMockitoSessionBuilder> mDynamicSessionBuilderConfigurator;
+        @Nullable
+        private final Runnable mAfterSessionFinishedCallback;
+
+        private SimpleStatickMockFixture(List<Class<?>> mockedStaticClasses,
+                List<Class<?>> spiedStaticClasses,
+                @Nullable Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator,
+                @Nullable Runnable afterSessionFinishedCallback) {
+            mMockedStaticClasses = mockedStaticClasses;
+            mSpiedStaticClasses = spiedStaticClasses;
+            mDynamicSessionBuilderConfigurator = dynamicSessionBuilderConfigurator;
+            mAfterSessionFinishedCallback = afterSessionFinishedCallback;
+        }
+
+        @Override
+        public StaticMockitoSessionBuilder setUpMockedClasses(
+                StaticMockitoSessionBuilder sessionBuilder) {
+            mMockedStaticClasses.forEach((c) -> sessionBuilder.mockStatic(c));
+            mSpiedStaticClasses.forEach((c) -> sessionBuilder.spyStatic(c));
+            if (mDynamicSessionBuilderConfigurator != null) {
+                mDynamicSessionBuilderConfigurator.visit(sessionBuilder);
+            }
+            return sessionBuilder;
+        }
+
+        @Override
+        public void setUpMockBehaviors() {
+        }
+
+        @Override
+        public void tearDown() {
+            try {
+                if (mAfterSessionFinishedCallback != null) {
+                    mAfterSessionFinishedCallback.run();
+                }
+            } finally {
+                if (VERBOSE) {
+                    Log.v(TAG, "calling Mockito.framework().clearInlineMocks()");
+                }
+                // When using inline mock maker, clean up inline mocks to prevent OutOfMemory
+                // errors. See https://github.com/mockito/mockito/issues/1614 and b/259280359.
+                Mockito.framework().clearInlineMocks();
+            }
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
deleted file mode 100644
index 48483a1..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
+++ /dev/null
@@ -1,249 +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.server;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import android.annotation.Nullable;
-import android.util.Log;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-
-import com.google.common.truth.Expect;
-import com.google.common.truth.StandardSubjectBuilder;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.mockito.Mockito;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-import java.lang.reflect.Constructor;
-
-/**
- * Base class to make it easier to write tests that uses {@code ExtendedMockito}.
- *
- */
-public abstract class ExtendedMockitoTestCase {
-
-    private static final String TAG = ExtendedMockitoTestCase.class.getSimpleName();
-
-    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
-    /**
-     * Number of invocations, used to force a failure on {@link #forceFailure(int, Class, String)}.
-     */
-    private static int sInvocationsCounter;
-
-    /**
-     * Sessions follow the "Highlander Rule": There can be only one!
-     *
-     * <p>So, we keep track of that and force-close it if needed.
-     */
-    @Nullable
-    private static MockitoSession sHighlanderSession;
-
-    /**
-     * Points to where the current session was created.
-     */
-    private static Exception sSessionCreationLocation;
-
-    private MockitoSession mSession;
-
-    protected final Expect mExpect = Expect.create();
-    protected final DumpableDumperRule mDumpableDumperRule = new DumpableDumperRule();
-
-    @Rule
-    public final RuleChain mTwoRingsOfPowerAndOneChainToRuleThemAll = RuleChain
-            .outerRule(mDumpableDumperRule)
-            .around(mExpect);
-
-    public ExtendedMockitoTestCase() {
-        sInvocationsCounter++;
-    }
-
-    @Before
-    public final void startSession() {
-        if (VERBOSE) {
-            Log.v(TAG, "startSession() for " + getTestName() + " on thread "
-                    + Thread.currentThread() + "; sHighlanderSession=" + sHighlanderSession);
-        }
-        createSessionLocation();
-        finishHighlanderSessionIfNeeded("startSession()");
-        StaticMockitoSessionBuilder builder = mockitoSession()
-                .initMocks(this)
-                .strictness(getSessionStrictness());
-        initializeSession(builder);
-        sHighlanderSession = mSession = builder.startMocking();
-    }
-
-    private void createSessionLocation() {
-        try {
-            sSessionCreationLocation = new Exception(getTestName());
-        } catch (Exception e) {
-            // Better safe than sorry...
-            Log.e(TAG, "Could not create sSessionCreationLocation with " + getTestName()
-                    + " on thread " + Thread.currentThread(), e);
-            sSessionCreationLocation = e;
-        }
-    }
-
-    /**
-     * Gets the session strictness.
-     *
-     * @return {@link Strictness.LENIENT} by default; subclasses can override.
-     */
-    protected Strictness getSessionStrictness() {
-        return Strictness.LENIENT;
-    }
-
-    /**
-     * Initializes the mockito session.
-     *
-     * <p>Typically used to define which classes should have static methods mocked or spied.
-     */
-    protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        if (VERBOSE) {
-            Log.v(TAG, "initializeSession()");
-        }
-    }
-
-    @After
-    public final void finishSession() throws Exception {
-        if (false) { // For obvious reasons, should NEVER be merged as true
-            forceFailure(1, RuntimeException.class, "to simulate an unfinished session");
-        }
-
-        // mSession.finishMocking() must ALWAYS be called (hence the over-protective try/finally
-        // statements), otherwise it would cause failures on future tests as mockito
-        // cannot start a session when a previous one is not finished
-        try {
-            if (VERBOSE) {
-                Log.v(TAG, "finishSession() for " + getTestName() + " on thread "
-                        + Thread.currentThread() + "; sHighlanderSession=" + sHighlanderSession);
-
-            }
-        } finally {
-            sHighlanderSession = null;
-            finishSessionMocking();
-            afterSessionFinished();
-        }
-    }
-
-    /**
-     * Called after the mockito session was finished
-     *
-     * <p>This method should be used by subclasses that MUST do their cleanup after the session is
-     * finished (as methods marked with {@link After} in the subclasses would be called BEFORE
-     * that).
-     */
-    protected void afterSessionFinished() {
-        if (VERBOSE) {
-            Log.v(TAG, "afterSessionFinished()");
-        }
-    }
-
-    private void finishSessionMocking() {
-        if (mSession == null) {
-            Log.w(TAG, getClass().getSimpleName() + ".finishSession(): no session");
-            return;
-        }
-        try {
-            mSession.finishMocking();
-        } finally {
-            // Shouldn't need to set mSession to null as JUnit always instantiate a new object,
-            // but it doesn't hurt....
-            mSession = null;
-            // When using inline mock maker, clean up inline mocks to prevent OutOfMemory
-            // errors. See https://github.com/mockito/mockito/issues/1614 and b/259280359.
-            Mockito.framework().clearInlineMocks();
-        }
-    }
-
-    private void finishHighlanderSessionIfNeeded(String where) {
-        if (sHighlanderSession == null) {
-            if (VERBOSE) {
-                Log.v(TAG, "finishHighlanderSessionIfNeeded(): sHighlanderSession already null");
-            }
-            return;
-        }
-
-        if (sSessionCreationLocation != null) {
-            if (VERBOSE) {
-                Log.e(TAG, where + ": There can be only one! Closing unfinished session, "
-                        + "created at", sSessionCreationLocation);
-            } else {
-                Log.e(TAG, where + ": There can be only one! Closing unfinished session, "
-                        + "created at " +  sSessionCreationLocation);
-            }
-        } else {
-            Log.e(TAG, where + ": There can be only one! Closing unfinished session created at "
-                    + "unknown location");
-        }
-        try {
-            sHighlanderSession.finishMocking();
-        } catch (Throwable t) {
-            if (VERBOSE) {
-                Log.e(TAG, "Failed to close unfinished session on " + getTestName(), t);
-            } else {
-                Log.e(TAG, "Failed to close unfinished session on " + getTestName() + ": " + t);
-            }
-        } finally {
-            if (VERBOSE) {
-                Log.v(TAG, "Resetting sHighlanderSession at finishHighlanderSessionIfNeeded()");
-            }
-            sHighlanderSession = null;
-        }
-    }
-
-    /**
-     * Forces a failure at the given invocation of a test method by throwing an exception.
-     */
-    protected final <T extends Throwable> void forceFailure(int invocationCount,
-            Class<T> failureClass, String reason) throws T {
-        if (sInvocationsCounter != invocationCount) {
-            Log.d(TAG, "forceFailure(" + invocationCount + "): no-op on invocation #"
-                    + sInvocationsCounter);
-            return;
-        }
-        String message = "Throwing on invocation #" + sInvocationsCounter + ": " + reason;
-        Log.e(TAG, message);
-        T throwable;
-        try {
-            Constructor<T> constructor = failureClass.getConstructor(String.class);
-            throwable = constructor.newInstance("Throwing on invocation #" + sInvocationsCounter
-                    + ": " + reason);
-        } catch (Exception e) {
-            throw new IllegalArgumentException("Could not create exception of class " + failureClass
-                    + " using msg='" + message + "' as constructor");
-        }
-        throw throwable;
-    }
-
-    protected final @Nullable String getTestName() {
-        return mDumpableDumperRule.getTestName();
-    }
-
-    protected final StandardSubjectBuilder expectWithMessage(String msg) {
-        return mExpect.withMessage(msg);
-    }
-
-    protected final StandardSubjectBuilder expectWithMessage(String format, Object...args) {
-        return mExpect.withMessage(format, args);
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/Visitor.java b/services/tests/mockingservicestests/src/com/android/server/Visitor.java
new file mode 100644
index 0000000..447910e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/Visitor.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+/**
+ * Generic visitor.
+ */
+public interface Visitor<V> {
+    void visit(V visitee);
+}
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 9dd2f82..b395f42 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -159,7 +159,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.dx.mockito.inline.extended.MockedVoidMethod;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
@@ -168,7 +167,7 @@
 import com.android.server.AppStateTracker;
 import com.android.server.AppStateTrackerImpl;
 import com.android.server.DeviceIdleInternal;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.SystemService;
@@ -182,6 +181,7 @@
 import libcore.util.EmptyArray;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -204,7 +204,7 @@
 @Presubmit
 @SuppressWarnings("GuardedBy")  // This test enforces synchronous behavior.
 @RunWith(AndroidJUnit4.class)
-public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase {
+public final class AlarmManagerServiceTest {
     private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
     private static final int SYSTEM_UI_UID = 12345;
     private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);
@@ -411,14 +411,9 @@
         }
     }
 
-    @Override
-    protected Strictness getSessionStrictness() {
-        return Strictness.WARN;
-    }
-
-    @Override
-    protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .setStrictness(Strictness.WARN)
             .spyStatic(ActivityManager.class)
             .mockStatic(CompatChanges.class)
             .spyStatic(DateFormat.class)
@@ -431,8 +426,10 @@
             .mockStatic(ServiceManager.class)
             .mockStatic(Settings.Global.class)
             .mockStatic(SystemProperties.class)
-            .spyStatic(UserHandle.class);
-    }
+            .spyStatic(UserHandle.class)
+            .afterSessionFinished(
+                    () -> LocalServices.removeServiceForTest(AlarmManagerInternal.class))
+            .build();
 
     @Before
     public final void setUp() {
@@ -3805,9 +3802,4 @@
         mListener.removeListenerAlarmsForCachedUid(TEST_CALLING_UID_2);
         assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
     }
-
-    @Override
-    public void afterSessionFinished() {
-        LocalServices.removeServiceForTest(AlarmManagerInternal.class);
-    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
index e01a9a9..9ceb5e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
@@ -31,11 +31,11 @@
 import android.util.Log;
 import android.view.Display;
 
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
 import com.android.server.am.ActivityManagerService.Injector;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 
@@ -45,7 +45,7 @@
  * Run as {@code atest
  * FrameworksMockingServicesTests:com.android.server.am.ActivityManagerServiceInjectorTest}
  */
-public final class ActivityManagerServiceInjectorTest extends ExtendedMockitoTestCase {
+public final class ActivityManagerServiceInjectorTest {
 
     private static final String TAG = ActivityManagerServiceInjectorTest.class.getSimpleName();
 
@@ -63,10 +63,10 @@
         when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
     }
 
-    @Override
-    protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder.spyStatic(UserManager.class);
-    }
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .spyStatic(UserManager.class)
+            .build();
 
     @Test
     public void testGetDisplayIdsForStartingBackgroundUsers_notSupported() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index dcdee37..95c2ed2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -78,18 +78,15 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
 import com.android.server.am.BroadcastQueueTest.SyncBarrier;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
@@ -98,8 +95,7 @@
 import java.util.List;
 
 @SmallTest
-@RunWith(MockitoJUnitRunner.class)
-public class BroadcastQueueModernImplTest extends ExtendedMockitoTestCase {
+public final class BroadcastQueueModernImplTest {
     private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
     private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1;
 
@@ -118,15 +114,13 @@
 
     BroadcastProcessQueue mHead;
 
-    @Override
-    protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder.spyStatic(FrameworkStatsLog.class);
-    }
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .spyStatic(FrameworkStatsLog.class)
+            .build();
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
         mHandlerThread = new HandlerThread(getClass().getSimpleName());
         mHandlerThread.start();
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index f8cfdf1..ec9e5b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -40,9 +40,8 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 import com.android.modules.utils.testing.TestableDeviceConfig;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.appop.AppOpsService;
@@ -69,7 +68,7 @@
  * atest FrameworksMockingServicesTests:CachedAppOptimizerTest
  */
 @Presubmit
-public final class CachedAppOptimizerTest extends ExtendedMockitoTestCase {
+public final class CachedAppOptimizerTest {
 
     private ServiceThread mThread;
 
@@ -93,10 +92,11 @@
     public final ApplicationExitInfoTest.ServiceThreadRule
             mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
 
-    @Override
-    protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        mDeviceConfig.setUpMockedClasses(builder);
-    }
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .dynamiclyConfigureSessionBuilder(
+                    sessionBuilder -> mDeviceConfig.setUpMockedClasses(sessionBuilder))
+            .build();
 
     @Before
     public void setUp() {
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 dc7f3cd..485ce33 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -143,8 +143,8 @@
     private static final String MOCKAPP5_PROCESSNAME = "test #5";
     private static final String MOCKAPP5_PACKAGENAME = "com.android.test.test5";
     private static final int MOCKAPP2_UID_OTHER = MOCKAPP2_UID + UserHandle.PER_USER_RANGE;
-    private static final int FIRST_CACHED_ADJ = ProcessList.CACHED_APP_MIN_ADJ
-            + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+    private static int sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ
+                                          + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
     private static Context sContext;
     private static PackageManagerInternal sPackageManagerInternal;
     private static ActivityManagerService sService;
@@ -208,6 +208,9 @@
                 new ActiveUids(sService, false));
         sService.mOomAdjuster.mAdjSeq = 10000;
         sService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        if (sService.mConstants.USE_TIERED_CACHED_ADJ) {
+            sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ + 10;
+        }
     }
 
     @AfterClass
@@ -834,7 +837,7 @@
         updateOomAdj(client, app);
         doReturn(null).when(sService).getTopApp();
 
-        assertProcStates(app, PROCESS_STATE_SERVICE, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+        assertProcStates(app, PROCESS_STATE_SERVICE, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -882,7 +885,7 @@
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         updateOomAdj(app);
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1286,7 +1289,7 @@
         bindProvider(app, app, null, null, false);
         updateOomAdj(app);
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1301,7 +1304,7 @@
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         updateOomAdj(app, client);
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -2378,8 +2381,12 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         final int userOwner = 0;
         final int userOther = 1;
-        final int cachedAdj1 = CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
-        final int cachedAdj2 = cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
+        final int cachedAdj1 = sService.mConstants.USE_TIERED_CACHED_ADJ
+                               ? CACHED_APP_MIN_ADJ + 10
+                               : CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+        final int cachedAdj2 = sService.mConstants.USE_TIERED_CACHED_ADJ
+                               ? CACHED_APP_MIN_ADJ + 10
+                               : cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
         doReturn(userOwner).when(sService.mUserController).getCurrentUserId();
 
         final ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
index 9f3bc33..b214787 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
@@ -29,7 +29,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExpectableTestCase;
 
 import libcore.io.Streams;
 
@@ -44,7 +44,7 @@
 import java.util.Objects;
 
 /** This class contains unit tests for the {@link CpuInfoReader}. */
-public final class CpuInfoReaderTest extends ExtendedMockitoTestCase {
+public final class CpuInfoReaderTest extends ExpectableTestCase {
     private static final String TAG = CpuInfoReaderTest.class.getSimpleName();
     private static final String ROOT_DIR_NAME = "CpuInfoReaderTest";
     private static final String VALID_CPUSET_DIR = "valid_cpuset";
@@ -426,7 +426,7 @@
                 .isNull();
     }
 
-    private static void compareCpuInfos(String message,
+    private void compareCpuInfos(String message,
             SparseArray<CpuInfoReader.CpuInfo> expected,
             SparseArray<CpuInfoReader.CpuInfo> actual) {
         assertWithMessage("%s. Total CPU infos", message).that(actual.size())
@@ -435,7 +435,7 @@
             int cpuCoreId = expected.keyAt(i);
             CpuInfoReader.CpuInfo expectedCpuInfo = expected.valueAt(i);
             CpuInfoReader.CpuInfo actualCpuInfo = actual.get(cpuCoreId);
-            assertWithMessage("%s. Core %d's CPU info", message, cpuCoreId).that(actualCpuInfo)
+            expectWithMessage("%s. Core %s's CPU info", message, cpuCoreId).that(actualCpuInfo)
                     .isEqualTo(expectedCpuInfo);
         }
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
index 7ab1363..49a2cc6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -32,16 +32,16 @@
 import android.os.Looper;
 import android.os.ServiceManager;
 
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 
-public final class CpuMonitorServiceTest extends ExtendedMockitoTestCase {
+public final class CpuMonitorServiceTest {
     private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG =
             new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
                     .addThreshold(30).addThreshold(70).build();
@@ -56,10 +56,10 @@
     private HandlerExecutor mHandlerExecutor;
     private CpuMonitorInternal mLocalService;
 
-    @Override
-    protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder.mockStatic(ServiceManager.class);
-    }
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .mockStatic(ServiceManager.class)
+            .build();
 
     @Before
     public void setUp() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
index b4104db..03f667f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
@@ -33,6 +33,7 @@
 
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.job.JobParameters;
 import android.app.job.JobService;
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
@@ -145,7 +146,7 @@
                 .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
 
-        coordinator.removeNotificationAssociation(jsc);
+        coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED);
         verify(mNotificationManagerInternal, never())
                 .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
                         anyInt(), anyInt());
@@ -166,7 +167,7 @@
                 .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
 
-        coordinator.removeNotificationAssociation(jsc);
+        coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED);
         verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid)));
@@ -288,7 +289,7 @@
                         eq(notificationId2), eq(notification2), eq(UserHandle.getUserId(uid)));
 
         // Remove the first job. Only the first notification should be removed.
-        coordinator.removeNotificationAssociation(jsc1);
+        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId1), eq(UserHandle.getUserId(uid)));
@@ -296,7 +297,7 @@
                 .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
                         eq(notificationId2), anyInt());
 
-        coordinator.removeNotificationAssociation(jsc2);
+        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId2), eq(UserHandle.getUserId(uid)));
@@ -331,12 +332,12 @@
                         eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
 
         // Remove the first job. The notification shouldn't be touched because of the 2nd job.
-        coordinator.removeNotificationAssociation(jsc1);
+        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
         inOrder.verify(mNotificationManagerInternal, never())
                 .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
                         anyInt(), anyInt());
 
-        coordinator.removeNotificationAssociation(jsc2);
+        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid)));
@@ -372,7 +373,7 @@
                         eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid2)));
 
         // Remove the first job. Only the first notification should be removed.
-        coordinator.removeNotificationAssociation(jsc1);
+        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid1), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid1)));
@@ -380,7 +381,7 @@
                 .cancelNotification(anyString(), anyString(), eq(uid2), anyInt(), any(),
                         anyInt(), anyInt());
 
-        coordinator.removeNotificationAssociation(jsc2);
+        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid2), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid2)));
@@ -417,7 +418,7 @@
                         eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
 
         // Remove the first job. Only the first notification should be removed.
-        coordinator.removeNotificationAssociation(jsc1);
+        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(pkg1), eq(pkg1), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid)));
@@ -425,12 +426,73 @@
                 .cancelNotification(anyString(), anyString(), eq(uid), anyInt(), any(),
                         anyInt(), anyInt());
 
-        coordinator.removeNotificationAssociation(jsc2);
+        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
         inOrder.verify(mNotificationManagerInternal)
                 .cancelNotification(eq(pkg2), eq(pkg2), eq(uid), eq(pid), any(),
                         eq(notificationId), eq(UserHandle.getUserId(uid)));
     }
 
+    @Test
+    public void testUserStop_SingleJob_DetachOnStop() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final Notification notification = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId = 23;
+
+        coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification,
+                JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+        verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
+
+        coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_USER);
+        verify(mNotificationManagerInternal)
+                .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(UserHandle.getUserId(uid)));
+    }
+
+    @Test
+    public void testUserStop_MultipleJobs_sameApp_EnqueueSameNotificationId_DetachOnStop() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc1 = mock(JobServiceContext.class);
+        final JobServiceContext jsc2 = mock(JobServiceContext.class);
+        final Notification notification1 = createValidNotification();
+        final Notification notification2 = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId = 23;
+
+        InOrder inOrder = inOrder(mNotificationManagerInternal);
+
+        coordinator.enqueueNotification(jsc1, TEST_PACKAGE, pid, uid, notificationId, notification1,
+                JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification1), eq(UserHandle.getUserId(uid)));
+
+        coordinator.enqueueNotification(jsc2, TEST_PACKAGE, pid, uid, notificationId, notification2,
+                JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), anyInt());
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
+
+        // Remove the first job. The notification shouldn't be touched because of the 2nd job.
+        coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_USER);
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), anyInt());
+
+        coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_USER);
+        inOrder.verify(mNotificationManagerInternal)
+                .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(UserHandle.getUserId(uid)));
+    }
+
     private Notification createValidNotification() {
         final Notification notification = mock(Notification.class);
         doReturn(mock(Icon.class)).when(notification).getSmallIcon();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 06ba5dd..3f5d113 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -664,7 +664,7 @@
     private fun mockQueryActivities(action: String, vararg activities: ActivityInfo) {
         whenever(mocks.componentResolver.queryActivities(any(),
                 argThat { intent: Intent? -> intent != null && (action == intent.action) },
-                nullable(), anyLong(), anyInt())) {
+                nullable(), anyLong(), anyInt(), anyInt())) {
             ArrayList(activities.asList().map { info: ActivityInfo? ->
                 ResolveInfo().apply { activityInfo = info }
             })
@@ -674,7 +674,7 @@
     private fun mockQueryServices(action: String, vararg services: ServiceInfo) {
         whenever(mocks.componentResolver.queryServices(any(),
                 argThat { intent: Intent? -> intent != null && (action == intent.action) },
-                nullable(), anyLong(), anyInt())) {
+                nullable(), anyLong(), anyInt(), anyInt())) {
             ArrayList(services.asList().map { info ->
                 ResolveInfo().apply { serviceInfo = info }
             })
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 1ed2f78..564893c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -40,9 +40,8 @@
 
 import androidx.test.annotation.UiThreadTest;
 
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
 import com.android.internal.widget.LockSettingsInternal;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.am.UserState;
 import com.android.server.pm.UserManagerService.UserData;
@@ -50,13 +49,14 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 
 /**
  * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
  */
-public final class UserManagerServiceTest extends ExtendedMockitoTestCase {
+public final class UserManagerServiceTest {
 
     private static final String TAG = UserManagerServiceTest.class.getSimpleName();
 
@@ -84,6 +84,13 @@
      */
     private static final int PROFILE_USER_ID = 643;
 
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .spyStatic(UserManager.class)
+            .spyStatic(LocalServices.class)
+            .mockStatic(Settings.Global.class)
+            .build();
+
     private final Object mPackagesLock = new Object();
     private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
             .getTargetContext();
@@ -109,14 +116,6 @@
      */
     private UserManagerInternal mUmi;
 
-    @Override
-    protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder
-                .spyStatic(UserManager.class)
-                .spyStatic(LocalServices.class)
-                .mockStatic(Settings.Global.class);
-    }
-
     @Before
     @UiThreadTest // Needed to initialize main handler
     public void setFixtures() {
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 38cf634..9aa53db 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
@@ -20,8 +20,6 @@
 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;
@@ -128,7 +126,8 @@
 
         listener.verify();
     }
-  /* TODO: re-add
+
+  /* TODO(b/261538337): re-add after the reverted CL is merged again
 
     @Test
     public void
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..1bf921c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -45,7 +45,8 @@
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.DumpableDumperRule;
+import com.android.server.ExpectableTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -64,7 +65,7 @@
  * is visible, display associated to the user, etc...) for each scenario (full user started on fg,
  * profile user started on bg, etc...).
  */
-abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
+abstract class UserVisibilityMediatorTestCase extends ExpectableTestCase {
 
     private static final String TAG = UserVisibilityMediatorTestCase.class.getSimpleName();
 
@@ -125,6 +126,8 @@
         mBackgroundUserOnDefaultDisplayAllowed = backgroundUserOnDefaultDisplayAllowed;
     }
 
+    protected final DumpableDumperRule mDumpableDumperRule = new DumpableDumperRule();
+
     @Before
     public final void setFixtures() {
         mHandler = Handler.getMain();
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
index cb59d37..02e46bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
@@ -16,16 +16,9 @@
 
 package com.android.server.utils;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-
 import android.util.Log;
 import android.util.Slog;
 
@@ -51,7 +44,6 @@
         mSession = mockitoSession()
                 .initMocks(this)
                 .mockStatic(Slog.class)
-                .spyStatic(Slogf.class) // for isLoggable only
                 .strictness(Strictness.LENIENT)
                 .startMocking();
     }
@@ -66,11 +58,6 @@
     }
 
     @Test
-    public void testIsLoggable() {
-        assertThat(Slogf.isLoggable(TAG, Log.VERBOSE)).isEqualTo(Log.isLoggable(TAG, Log.VERBOSE));
-    }
-
-    @Test
     public void testV_msg() {
         Slogf.v(TAG, "msg");
 
@@ -85,42 +72,20 @@
     }
 
     @Test
-    public void testV_msgFormatted_enabled() {
-        enableLogging(Log.VERBOSE);
-
+    public void testV_msgFormatted() {
         Slogf.v(TAG, "msg in a %s", "bottle");
 
         verify(()-> Slog.v(TAG, "msg in a bottle"));
     }
 
     @Test
-    public void testV_msgFormatted_disabled() {
-        disableLogging(Log.VERBOSE);
-
-        Slogf.v(TAG, "msg in a %s", "bottle");
-
-        verify(()-> Slog.v(eq(TAG), any()), never());
-    }
-
-    @Test
-    public void testV_msgFormattedWithThrowable_enabled() {
-        enableLogging(Log.VERBOSE);
-
+    public void testV_msgFormattedWithThrowable() {
         Slogf.v(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.v(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
-    public void testV_msgFormattedWithException_disabled() {
-        disableLogging(Log.VERBOSE);
-
-        Slogf.v(TAG, "msg in a %s", "bottle");
-
-        verify(()-> Slog.v(eq(TAG), any(String.class), any(Throwable.class)), never());
-    }
-
-    @Test
     public void testD_msg() {
         Slogf.d(TAG, "msg");
 
@@ -135,42 +100,20 @@
     }
 
     @Test
-    public void testD_msgFormatted_enabled() {
-        enableLogging(Log.DEBUG);
-
+    public void testD_msgFormatted() {
         Slogf.d(TAG, "msg in a %s", "bottle");
 
         verify(()-> Slog.d(TAG, "msg in a bottle"));
     }
 
     @Test
-    public void testD_msgFormatted_disabled() {
-        disableLogging(Log.DEBUG);
-
-        Slogf.d(TAG, "msg in a %s", "bottle");
-
-        verify(()-> Slog.d(eq(TAG), any()), never());
-    }
-
-    @Test
-    public void testD_msgFormattedWithThrowable_enabled() {
-        enableLogging(Log.DEBUG);
-
+    public void testD_msgFormattedWithThrowable() {
         Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.d(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
-    public void testD_msgFormattedWithException_disabled() {
-        disableLogging(Log.DEBUG);
-
-        Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
-
-        verify(()-> Slog.d(eq(TAG), any(String.class), any(Throwable.class)), never());
-    }
-
-    @Test
     public void testI_msg() {
         Slogf.i(TAG, "msg");
 
@@ -185,42 +128,20 @@
     }
 
     @Test
-    public void testI_msgFormatted_enabled() {
-        enableLogging(Log.INFO);
-
+    public void testI_msgFormatted() {
         Slogf.i(TAG, "msg in a %s", "bottle");
 
         verify(()-> Slog.i(TAG, "msg in a bottle"));
     }
 
     @Test
-    public void testI_msgFormatted_disabled() {
-        disableLogging(Log.INFO);
-
-        Slogf.i(TAG, "msg in a %s", "bottle");
-
-        verify(()-> Slog.i(eq(TAG), any()), never());
-    }
-
-    @Test
-    public void testI_msgFormattedWithThrowable_enabled() {
-        enableLogging(Log.INFO);
-
+    public void testI_msgFormattedWithThrowable() {
         Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.i(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
-    public void testI_msgFormattedWithException_disabled() {
-        disableLogging(Log.INFO);
-
-        Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
-
-        verify(()-> Slog.i(eq(TAG), any(String.class), any(Throwable.class)), never());
-    }
-
-    @Test
     public void testW_msg() {
         Slogf.w(TAG, "msg");
 
@@ -242,42 +163,20 @@
     }
 
     @Test
-    public void testW_msgFormatted_enabled() {
-        enableLogging(Log.WARN);
-
+    public void testW_msgFormatted() {
         Slogf.w(TAG, "msg in a %s", "bottle");
 
         verify(()-> Slog.w(TAG, "msg in a bottle"));
     }
 
     @Test
-    public void testW_msgFormatted_disabled() {
-        disableLogging(Log.WARN);
-
-        Slogf.w(TAG, "msg in a %s", "bottle");
-
-        verify(()-> Slog.w(eq(TAG), any(String.class)), never());
-    }
-
-    @Test
-    public void testW_msgFormattedWithThrowable_enabled() {
-        enableLogging(Log.WARN);
-
+    public void testW_msgFormattedWithThrowable() {
         Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.w(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
-    public void testW_msgFormattedWithException_disabled() {
-        disableLogging(Log.WARN);
-
-        Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
-
-        verify(()-> Slog.w(eq(TAG), any(String.class), any(Throwable.class)), never());
-    }
-
-    @Test
     public void testE_msg() {
         Slogf.e(TAG, "msg");
 
@@ -292,42 +191,20 @@
     }
 
     @Test
-    public void testE_msgFormatted_enabled() {
-        enableLogging(Log.ERROR);
-
+    public void testE_msgFormatted() {
         Slogf.e(TAG, "msg in a %s", "bottle");
 
         verify(()-> Slog.e(TAG, "msg in a bottle"));
     }
 
     @Test
-    public void testE_msgFormatted_disabled() {
-        disableLogging(Log.ERROR);
-
-        Slogf.e(TAG, "msg in a %s", "bottle");
-
-        verify(()-> Slog.e(eq(TAG), any()), never());
-    }
-
-    @Test
-    public void testE_msgFormattedWithThrowable_enabled() {
-        enableLogging(Log.ERROR);
-
+    public void testE_msgFormattedWithThrowable() {
         Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
 
         verify(()-> Slog.e(TAG, "msg in a bottle", mThrowable));
     }
 
     @Test
-    public void testE_msgFormattedWithException_disabled() {
-        disableLogging(Log.ERROR);
-
-        Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
-
-        verify(()-> Slog.e(eq(TAG), any(String.class), any(Throwable.class)), never());
-    }
-
-    @Test
     public void testWtf_msg() {
         Slogf.wtf(TAG, "msg");
 
@@ -382,16 +259,4 @@
 
         verify(()-> Slog.wtf(TAG, "msg in a bottle", mThrowable));
     }
-
-    private void enableLogging(@Log.Level int level) {
-        setIsLogging(level, true);
-    }
-
-    private void disableLogging(@Log.Level int level) {
-        setIsLogging(level, false);
-    }
-
-    private void setIsLogging(@Log.Level int level, boolean value) {
-        doReturn(value).when(() -> Slogf.isLoggable(TAG, level));
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 4249405..503e579 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -284,7 +284,7 @@
         verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY),
                 eq(mTestableContext), anyInt(), any(), eq(mMockSecurityPolicy),
                 eq(mA11yms), eq(mA11yms.getTraceManager()),
-                eq(mMockWindowManagerService), eq(mMockA11yWindowManager));
+                eq(mMockWindowManagerService));
     }
 
     @SmallTest
@@ -296,7 +296,7 @@
         assertThrows(SecurityException.class,
                 () -> mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY));
         verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
-                any(), any(), any(), any());
+                any(), any(), any());
     }
 
     @SmallTest
@@ -305,7 +305,7 @@
         assertThrows(IllegalArgumentException.class,
                 () -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.DEFAULT_DISPLAY));
         verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
-                any(), any(), any(), any());
+                any(), any(), any());
     }
 
     @SmallTest
@@ -314,7 +314,7 @@
         assertThrows(IllegalArgumentException.class,
                 () -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.INVALID_DISPLAY));
         verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
-                any(), any(), any(), any());
+                any(), any(), any());
     }
 
     @SmallTest
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 51d3bae..306ce4d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -44,6 +44,8 @@
 import android.graphics.PointF;
 import android.os.Handler;
 import android.os.Message;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
 import android.testing.TestableContext;
 import android.util.DebugUtils;
 import android.view.InputDevice;
@@ -507,6 +509,91 @@
         verify(mWindowMagnificationPromptController).showNotificationIfNeeded();
     }
 
+    @Test
+    public void testTransitToPanningState_scaleDifferenceOverThreshold_startDetecting() {
+        final float scale = 2.0f;
+        final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
+                .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+        final float persistedScale = (1.0f + threshold) * scale + 1.0f;
+        mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
+                DEFAULT_Y, /* animate= */ false,
+                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+        mFullScreenMagnificationController.persistScale(DISPLAY_0);
+        mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
+                DEFAULT_Y, /* animate= */ false,
+                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+
+        mMgh.transitionTo(mMgh.mPanningScalingState);
+
+        assertTrue(mMgh.mPanningScalingState.mDetectingPassPersistedScale);
+
+        mMgh.clearAndTransitionToStateDetecting();
+        mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+    }
+
+    @Test
+    public void testTransitToPanningState_scaleDifferenceLessThanThreshold_doNotDetect() {
+        final float scale = 2.0f;
+        final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
+                .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+        final float persistedScale = (1.0f + threshold) * scale - 0.1f;
+        mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
+                DEFAULT_Y, /* animate= */ false,
+                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+        mFullScreenMagnificationController.persistScale(DISPLAY_0);
+        mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
+                DEFAULT_Y, /* animate= */ false,
+                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+
+        mMgh.transitionTo(mMgh.mPanningScalingState);
+
+        assertFalse(mMgh.mPanningScalingState.mDetectingPassPersistedScale);
+
+        mMgh.clearAndTransitionToStateDetecting();
+        mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+    }
+
+    @Test
+    public void testPanningScaleToPersistedScale_detecting_vibrateAndClear() {
+        Vibrator vibrator = mock(Vibrator.class);
+        mContext.addMockSystemService(Vibrator.class, vibrator);
+
+        mMgh.mPanningScalingState.mDetectingPassPersistedScale = true;
+
+        final float persistedScale =
+                mFullScreenMagnificationController.getPersistedScale(DISPLAY_0);
+
+        mMgh.transitionTo(mMgh.mPanningScalingState);
+        mMgh.mPanningScalingState.setScaleAndClearIfNeeded(persistedScale, DEFAULT_X, DEFAULT_Y);
+
+        verify(vibrator).vibrate(any(VibrationEffect.class));
+        assertFalse(mMgh.mPanningScalingState.mScaling);
+
+        mMgh.clearAndTransitionToStateDetecting();
+        mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+    }
+
+    @Test
+    public void testPanningScaleOverThreshold_notDetecting_startDetecting() {
+        final float persistedScale =
+                mFullScreenMagnificationController.getPersistedScale(DISPLAY_0);
+
+        mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
+                DEFAULT_Y, /* animate= */ false,
+                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+        mMgh.transitionTo(mMgh.mPanningScalingState);
+
+        final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
+                .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+        final float scale = (1.0f + threshold) * persistedScale + 1.0f;
+        mMgh.mPanningScalingState.setScaleAndClearIfNeeded(scale, DEFAULT_X, DEFAULT_Y);
+
+        assertTrue(mMgh.mPanningScalingState.mDetectingPassPersistedScale);
+
+        mMgh.clearAndTransitionToStateDetecting();
+        mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+    }
+
     private void assertActionsInOrder(List<MotionEvent> actualEvents,
             List<Integer> expectedActions) {
         assertTrue(actualEvents.size() == expectedActions.size());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index b0c3a6e..cf650d2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.hardware.biometrics.AuthenticateOptions;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.IBiometricService;
@@ -681,7 +682,37 @@
         TestableLooper.get(this).processAllMessages();
     }
 
-    private static class TestAuthenticationClient extends AuthenticationClient<Object> {
+    private static class TestAuthenticateOptions implements AuthenticateOptions {
+        @Override
+        public int getUserId() {
+            return 0;
+        }
+
+        @Override
+        public int getSensorId() {
+            return TEST_SENSOR_ID;
+        }
+
+        @Override
+        public int getDisplayState() {
+            return DISPLAY_STATE_UNKNOWN;
+        }
+
+        @NonNull
+        @Override
+        public String getOpPackageName() {
+            return "some.test.name";
+        }
+
+        @Nullable
+        @Override
+        public String getAttributionTag() {
+            return null;
+        }
+    }
+
+    private static class TestAuthenticationClient
+            extends AuthenticationClient<Object, TestAuthenticateOptions> {
         boolean mStartedHal = false;
         boolean mStoppedHal = false;
         boolean mDestroyed = false;
@@ -700,9 +731,10 @@
                 @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
                 @NonNull ClientMonitorCallbackConverter listener, int cookie,
                 @NonNull BiometricContext biometricContext) {
-            super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
-                    false /* restricted */, TAG, cookie, false /* requireConfirmation */,
-                    TEST_SENSOR_ID, mock(BiometricLogger.class), biometricContext,
+            super(context, lazyDaemon, token, listener, 0 /* operationId */,
+                    false /* restricted */, new TestAuthenticateOptions(), cookie,
+                    false /* requireConfirmation */,
+                    mock(BiometricLogger.class), biometricContext,
                     true /* isStrongBiometric */, null /* taskStackListener */,
                     null /* lockoutTracker */, false /* isKeyguard */,
                     true /* shouldVibrate */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 184a556..3ff802c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -33,6 +33,7 @@
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -153,14 +154,18 @@
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
         final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder()
+                .setOpPackageName("test-owner")
+                .setUserId(5)
+                .setSensorId(9)
+                .build();
         return new FaceAuthenticationClient(mContext, () -> aidl, mToken,
-                2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
-                false /* restricted */, "test-owner", 4 /* cookie */,
-                false /* requireConfirmation */, 9 /* sensorId */,
+                2 /* requestId */, mClientMonitorCallbackConverter, OP_ID,
+                false /* restricted */, options, 4 /* cookie */,
+                false /* requireConfirmation */,
                 mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
                 mUsageStats, null /* mLockoutCache */, false /* allowBackgroundAuthentication */,
-                null /* sensorPrivacyManager */,
-                0 /* biometricStrength */) {
+                null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
             @Override
             protected ActivityTaskManager getActivityTaskManager() {
                 return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index e0fdb8c..c4c5505 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import android.hardware.biometrics.face.ISession;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -112,8 +113,13 @@
 
         final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
         return new FaceDetectClient(mContext, () -> aidl, mToken,
-                99 /* requestId */, mClientMonitorCallbackConverter, USER_ID,
-                "own-it", 5 /* sensorId */, mBiometricLogger, mBiometricContext,
+                99 /* requestId */, mClientMonitorCallbackConverter,
+                new FaceAuthenticateOptions.Builder()
+                        .setUserId(USER_ID)
+                        .setSensorId(5)
+                        .setOpPackageName("own-it")
+                        .build(),
+                mBiometricLogger, mBiometricContext,
                 false /* isStrongBiometric */, null /* sensorPrivacyManager */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
index 3b66eab..54d6478 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
@@ -144,7 +144,7 @@
 
     @Test
     public void testAuthentication_enrollmentCallbackNeverNotified() {
-        AuthenticationClient<?> client = mock(AuthenticationClient.class);
+        AuthenticationClient<?, ?> client = mock(AuthenticationClient.class);
         mCallback.onClientFinished(client, true /* success */);
         verify(mBiometricStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
                 anyBoolean());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index a4048a2..25a700a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -16,24 +16,35 @@
 
 package com.android.server.biometrics.sensors.fingerprint;
 
+import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.app.AppOpsManager.OP_USE_BIOMETRIC;
+import static android.app.AppOpsManager.OP_USE_FINGERPRINT;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.fingerprint.FingerprintManager.SENSOR_ID_ANY;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AppOpsManager;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.IBiometricService;
-import android.hardware.biometrics.common.CommonProps;
-import android.hardware.biometrics.common.SensorStrength;
-import android.hardware.biometrics.fingerprint.FingerprintSensorType;
-import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.SensorLocation;
-import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.testing.TestableContext;
@@ -44,10 +55,13 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -64,6 +78,8 @@
     private static final int ID_VIRTUAL = 6;
     private static final String NAME_DEFAULT = "default";
     private static final String NAME_VIRTUAL = "virtual";
+    private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS =
+            List.of();
 
     @Rule
     public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -74,47 +90,80 @@
     public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
 
     @Mock
+    private AppOpsManager mAppOpsManager;
+    @Mock
     private BiometricContext mBiometricContext;
     @Mock
     private IBiometricService mIBiometricService;
     @Mock
-    private IFingerprint mIFingerprintDefault;
+    private FingerprintProvider mFingerprintDefault;
     @Mock
-    private IFingerprint mIFingerprintVirtual;
+    private FingerprintProvider mFingerprintVirtual;
+    @Mock
+    private IFingerprintServiceReceiver mServiceReceiver;
+    @Mock
+    private IBinder mToken;
 
-    private final SensorProps mSensorPropsDefault = createProps(ID_DEFAULT,
-            SensorStrength.STRONG, FingerprintSensorType.POWER_BUTTON);
-    private final SensorProps mSensorPropsVirtual = createProps(ID_VIRTUAL,
-            SensorStrength.STRONG, FingerprintSensorType.UNDER_DISPLAY_OPTICAL);
+    @Captor
+    private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor;
+
+    private final FingerprintSensorPropertiesInternal mSensorPropsDefault =
+            new FingerprintSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
+                    2 /* maxEnrollmentsPerUser */,
+                    List.of(),
+                    TYPE_REAR,
+                    false /* resetLockoutRequiresHardwareAuthToken */);
+    private final FingerprintSensorPropertiesInternal mSensorPropsVirtual =
+            new FingerprintSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG,
+                    2 /* maxEnrollmentsPerUser */,
+                    List.of(),
+                    TYPE_UDFPS_OPTICAL,
+                    false /* resetLockoutRequiresHardwareAuthToken */);
     private FingerprintService mService;
 
     @Before
     public void setup() throws Exception {
-        when(mIFingerprintDefault.getSensorProps()).thenReturn(
-                new SensorProps[]{mSensorPropsDefault});
-        when(mIFingerprintVirtual.getSensorProps()).thenReturn(
-                new SensorProps[]{mSensorPropsVirtual});
+        when(mFingerprintDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
+        when(mFingerprintVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+        when(mFingerprintDefault.containsSensor(anyInt()))
+                .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
+        when(mFingerprintVirtual.containsSensor(anyInt()))
+                .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
 
-        mContext.getTestablePermissions().setPermission(
-                USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
+        mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
+        for (int permission : List.of(OP_USE_BIOMETRIC, OP_USE_FINGERPRINT)) {
+            when(mAppOpsManager.noteOp(eq(permission), anyInt(), any(), any(), any()))
+                    .thenReturn(AppOpsManager.MODE_ALLOWED);
+        }
+
+        for (String permission : List.of(USE_BIOMETRIC, USE_BIOMETRIC_INTERNAL)) {
+            mContext.getTestablePermissions().setPermission(
+                    permission, PackageManager.PERMISSION_GRANTED);
+        }
     }
 
     private void initServiceWith(String... aidlInstances) {
         mService = new FingerprintService(mContext, mBiometricContext,
                 () -> mIBiometricService,
                 () -> aidlInstances,
-                (fqName) -> {
-                    if (fqName.endsWith(NAME_DEFAULT)) return mIFingerprintDefault;
-                    if (fqName.endsWith(NAME_VIRTUAL)) return mIFingerprintVirtual;
+                (name) -> {
+                    if (NAME_DEFAULT.equals(name)) return mFingerprintDefault;
+                    if (NAME_VIRTUAL.equals(name)) return mFingerprintVirtual;
                     return null;
                 });
     }
 
+    private void initServiceWithAndWait(String... aidlInstances) throws Exception {
+        initServiceWith(aidlInstances);
+        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+        waitForRegistration();
+    }
+
     @Test
     public void registerAuthenticators_defaultOnly() throws Exception {
         initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
 
-        mService.mServiceWrapper.registerAuthenticators(List.of());
+        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
@@ -126,7 +175,7 @@
         Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
                 Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
 
-        mService.mServiceWrapper.registerAuthenticators(List.of());
+        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -136,7 +185,7 @@
     public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
         initServiceWith(NAME_VIRTUAL);
 
-        mService.mServiceWrapper.registerAuthenticators(List.of());
+        mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
         waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -155,13 +204,47 @@
         latch.await(5, TimeUnit.SECONDS);
     }
 
-    private static SensorProps createProps(int id, byte strength, byte type) {
-        final SensorProps props = new SensorProps();
-        props.commonProps = new CommonProps();
-        props.commonProps.sensorId = id;
-        props.commonProps.sensorStrength = strength;
-        props.sensorType = type;
-        props.sensorLocations = new SensorLocation[]{new SensorLocation()};
-        return props;
+    @Test
+    public void authenticateWithDefaultSensorId() throws Exception {
+        initServiceWithAndWait(NAME_DEFAULT, NAME_VIRTUAL);
+
+        final long operationId = 2;
+        mService.mServiceWrapper.authenticate(mToken, operationId, mServiceReceiver,
+                new FingerprintAuthenticateOptions.Builder()
+                        .setSensorId(SENSOR_ID_ANY)
+                        .build());
+
+        final FingerprintAuthenticateOptions options =
+                verifyAuthenticateWithNewRequestId(mFingerprintDefault, operationId);
+        assertThat(options.getSensorId()).isEqualTo(ID_DEFAULT);
+        verifyNoAuthenticate(mFingerprintVirtual);
+    }
+
+
+    private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
+            FingerprintProvider provider, long operationId) {
+        return verifyAuthenticateWithNewRequestId(
+                provider, operationId, true /* shouldSchedule */);
+    }
+
+    private void verifyNoAuthenticate(FingerprintProvider provider) {
+        verifyAuthenticateWithNewRequestId(
+                provider, 0 /* operationId */, false /* shouldSchedule */);
+    }
+
+    private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
+            FingerprintProvider provider, long operationId, boolean shouldSchedule) {
+        verify(provider, shouldSchedule ? times(1) : never())
+                .scheduleAuthenticate(eq(mToken), eq(operationId), anyInt(), any(),
+                        mAuthenticateOptionsCaptor.capture(), anyBoolean(), anyInt(),
+                        anyBoolean());
+        verify(provider, never()).scheduleAuthenticate(eq(mToken), anyLong(),
+                anyInt(), any(), mAuthenticateOptionsCaptor.capture(), anyLong(),
+                anyBoolean(), anyInt(), anyBoolean());
+
+        if (shouldSchedule) {
+            return mAuthenticateOptionsCaptor.getValue();
+        }
+        return null;
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 99f7905..f0f975c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -41,6 +41,7 @@
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -414,11 +415,16 @@
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
         final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+        final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder()
+                .setOpPackageName("test-owner")
+                .setUserId(5)
+                .setSensorId(9)
+                .build();
         return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,
-                REQUEST_ID, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
-                false /* restricted */, "test-owner", 4 /* cookie */,
+                REQUEST_ID, mClientMonitorCallbackConverter, OP_ID,
+                false /* restricted */, options, 4 /* cookie */,
                 false /* requireConfirmation */,
-                9 /* sensorId */, mBiometricLogger, mBiometricContext,
+                mBiometricLogger, mBiometricContext,
                 true /* isStrongBiometric */,
                 null /* taskStackListener */, null /* lockoutCache */,
                 mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 2dbd8f6..e741e44 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -117,8 +118,13 @@
 
         final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
         return new FingerprintDetectClient(mContext, () -> aidl, mToken,
-                6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */,
-                "a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext,
+                6 /* requestId */, mClientMonitorCallbackConverter,
+                new FingerprintAuthenticateOptions.Builder()
+                        .setUserId(2)
+                        .setSensorId(1)
+                        .setOpPackageName("a-test")
+                        .build(),
+                mBiometricLogger, mBiometricContext,
                 mUdfpsOverlayController, null, true /* isStrongBiometric */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 0cd50f0..cc6f7c2 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -67,7 +68,11 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.hardware.Sensor;
+import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IDisplayManager;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.input.IInputManager;
 import android.hardware.input.VirtualDpadConfig;
 import android.hardware.input.VirtualKeyEvent;
@@ -144,6 +149,7 @@
     private static final String DEVICE_NAME_3 = "device name 3";
     private static final int DISPLAY_ID_1 = 2;
     private static final int DISPLAY_ID_2 = 3;
+    private static final int NON_EXISTENT_DISPLAY_ID = 42;
     private static final int DEVICE_OWNER_UID_1 = 50;
     private static final int DEVICE_OWNER_UID_2 = 51;
     private static final int UID_1 = 0;
@@ -162,6 +168,8 @@
     private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
     private static final int VIRTUAL_DEVICE_ID_1 = 42;
     private static final int VIRTUAL_DEVICE_ID_2 = 43;
+    private static final VirtualDisplayConfig VIRTUAL_DISPLAY_CONFIG =
+            new VirtualDisplayConfig.Builder("virtual_display", 640, 480, 400).build();
     private static final VirtualDpadConfig DPAD_CONFIG =
             new VirtualDpadConfig.Builder()
                     .setVendorId(VENDOR_ID)
@@ -221,6 +229,8 @@
     @Mock
     private DisplayManagerInternal mDisplayManagerInternalMock;
     @Mock
+    private IDisplayManager mIDisplayManager;
+    @Mock
     private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
     @Mock
     private DevicePolicyManager mDevicePolicyManagerMock;
@@ -237,6 +247,8 @@
     @Mock
     private IVirtualDeviceSoundEffectListener mSoundEffectListener;
     @Mock
+    private IVirtualDisplayCallback mVirtualDisplayCallback;
+    @Mock
     private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
     @Mock
     private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener;
@@ -271,9 +283,13 @@
 
     private Intent createRestrictedActivityBlockedIntent(List displayCategories,
             String targetDisplayCategory) {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(displayCategories), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        when(mDisplayManagerInternalMock.createVirtualDisplay(any(), any(), any(), any(),
+                eq(NONBLOCKED_APP_PACKAGE_NAME))).thenReturn(DISPLAY_ID_1);
+        VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("display", 640, 480,
+                420).setDisplayCategories(displayCategories).build();
+        mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback,
+                NONBLOCKED_APP_PACKAGE_NAME);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -327,6 +343,7 @@
         mContext = Mockito.spy(new ContextWrapper(
                 InstrumentationRegistry.getInstrumentation().getTargetContext()));
         doReturn(mContext).when(mContext).createContextAsUser(eq(Process.myUserHandle()), anyInt());
+        doNothing().when(mContext).sendBroadcastAsUser(any(), any());
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
                 mDevicePolicyManagerMock);
 
@@ -369,15 +386,13 @@
 
     @Test
     public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() {
-        mDeviceImpl.mVirtualDisplayIds.remove(DISPLAY_ID_1);
-
-        assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
+        assertThat(mVdm.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID))
                 .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
     public void getDeviceIdForDisplayId_withValidVirtualDisplayId_returnsDeviceId() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
                 .isEqualTo(mDeviceImpl.getDeviceId());
@@ -503,10 +518,9 @@
 
     @Test
     public void getDeviceIdsForUid_differentUidOnDevice_returnsNull() {
-        GenericWindowPolicyController gwpc =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_2));
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_2));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).isEmpty();
@@ -514,10 +528,9 @@
 
     @Test
     public void getDeviceIdsForUid_oneUidOnDevice_returnsCorrectId() {
-        GenericWindowPolicyController gwpc =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_1));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
@@ -525,10 +538,10 @@
 
     @Test
     public void getDeviceIdsForUid_twoUidsOnDevice_returnsCorrectId() {
-        GenericWindowPolicyController gwpc =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_1, UID_2));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
@@ -538,11 +551,10 @@
     public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() {
         VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
                 DEVICE_OWNER_UID_2);
+        addVirtualDisplay(secondDevice, DISPLAY_ID_2);
 
-        GenericWindowPolicyController gwpc =
-                secondDevice.createWindowPolicyController(new ArrayList<>());
-        secondDevice.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_2);
-        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+        secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
+                Sets.newArraySet(UID_1));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(secondDevice.getDeviceId());
@@ -550,16 +562,16 @@
 
     @Test
     public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() {
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
                 DEVICE_OWNER_UID_2);
-        GenericWindowPolicyController gwpc1 =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        GenericWindowPolicyController gwpc2 =
-                secondDevice.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc1, DISPLAY_ID_1);
-        secondDevice.onVirtualDisplayCreatedLocked(gwpc2, DISPLAY_ID_2);
-        gwpc1.onRunningAppsChanged(Sets.newArraySet(UID_1));
-        gwpc2.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+        addVirtualDisplay(secondDevice, DISPLAY_ID_2);
+
+
+        mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+                Sets.newArraySet(UID_1));
+        secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
+                Sets.newArraySet(UID_1, UID_2));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
         assertThat(deviceIds).containsExactly(
@@ -568,8 +580,7 @@
 
     @Test
     public void getPreferredLocaleListForApp_keyboardAttached_returnLocaleHints() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
 
         mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1));
@@ -609,8 +620,8 @@
                         .setLanguageTag("fr-FR")
                         .build();
 
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        secondDevice.mVirtualDisplayIds.add(DISPLAY_ID_2);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        addVirtualDisplay(secondDevice, DISPLAY_ID_2);
 
         mDeviceImpl.createVirtualKeyboard(firstKeyboardConfig, BINDER);
         secondDevice.createVirtualKeyboard(secondKeyboardConfig, secondBinder);
@@ -640,10 +651,9 @@
 
     @Test
     public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         // This call should not throw any exceptions.
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+        mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1);
     }
 
     @Test
@@ -659,8 +669,8 @@
     @Test
     public void onVirtualDisplayRemovedLocked_listenersNotified() {
         mLocalService.registerVirtualDisplayListener(mDisplayListener);
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID_1);
         TestableLooper.get(this).processAllMessages();
@@ -723,8 +733,7 @@
         verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                 nullable(String.class), anyInt(), eq(null));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                 nullable(String.class), eq(DISPLAY_ID_1), eq(null));
@@ -733,12 +742,9 @@
     @Test
     public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
             throws RemoteException {
-        GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController(
-                new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         assertThrows(IllegalStateException.class,
-                () -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1));
+                () -> addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1));
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
@@ -749,13 +755,12 @@
     public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() {
         final int unknownDisplayId = 999;
         assertThrows(IllegalStateException.class,
-                () -> mDeviceImpl.onVirtualDisplayRemovedLocked(unknownDisplayId));
+                () -> mDeviceImpl.onVirtualDisplayRemoved(unknownDisplayId));
     }
 
     @Test
     public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -764,14 +769,13 @@
                 nullable(String.class), eq(DISPLAY_ID_1), eq(null));
 
         IBinder wakeLock = wakeLockCaptor.getValue();
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+        mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1);
         verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt());
     }
 
     @Test
     public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -825,7 +829,7 @@
 
     @Test
     public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         VirtualTouchscreenConfig positiveConfig =
                 new VirtualTouchscreenConfig.Builder(
                         /* touchscrenWidth= */ 600, /* touchscreenHeight= */ 800)
@@ -863,7 +867,7 @@
 
     @Test
     public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         VirtualNavigationTouchpadConfig positiveConfig =
                 new VirtualNavigationTouchpadConfig.Builder(
                         /* touchpadHeight= */ 50, /* touchpadWidth= */ 50)
@@ -888,7 +892,7 @@
 
     @Test
     public void createVirtualDpad_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
@@ -897,7 +901,7 @@
 
     @Test
     public void createVirtualKeyboard_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
@@ -906,7 +910,7 @@
 
     @Test
     public void createVirtualMouse_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
@@ -915,7 +919,7 @@
 
     @Test
     public void createVirtualTouchscreen_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
@@ -924,7 +928,7 @@
 
     @Test
     public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
@@ -934,7 +938,7 @@
 
     @Test
     public void onAudioSessionStarting_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
             assertThrows(SecurityException.class,
                     () -> mDeviceImpl.onAudioSessionStarting(
@@ -951,7 +955,7 @@
 
     @Test
     public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER);
         assertWithMessage("Virtual dpad should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -961,7 +965,7 @@
 
     @Test
     public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -971,7 +975,7 @@
 
     @Test
     public void createVirtualKeyboard_keyboardCreated_localeUpdated() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.getInputDeviceDescriptors())
@@ -992,7 +996,7 @@
                         .setAssociatedDisplayId(DISPLAY_ID_1)
                         .build();
 
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(configWithoutExplicitLayoutInfo, BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
                 .that(mInputController.getInputDeviceDescriptors())
@@ -1005,7 +1009,7 @@
 
     @Test
     public void virtualDeviceWithoutKeyboard_noLocaleUpdate() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         // no preceding call to createVirtualKeyboard()
         assertThat(mDeviceImpl.getDeviceLocaleList()).isNull();
@@ -1013,7 +1017,7 @@
 
     @Test
     public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER);
         assertWithMessage("Virtual mouse should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1023,7 +1027,7 @@
 
     @Test
     public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
         assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1033,7 +1037,7 @@
 
     @Test
     public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER);
         assertWithMessage("Virtual navigation touchpad should register fd when the display matches")
                 .that(
@@ -1055,8 +1059,7 @@
 
     @Test
     public void onAudioSessionStarting_hasVirtualAudioController() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
 
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
@@ -1065,8 +1068,7 @@
 
     @Test
     public void onAudioSessionEnded_noVirtualAudioController() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.onAudioSessionEnded();
@@ -1076,8 +1078,7 @@
 
     @Test
     public void close_cleanVirtualAudioController() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.close();
@@ -1321,9 +1322,9 @@
 
     @Test
     public void setShowPointerIcon_setsValueForAllDisplays() {
-        mDeviceImpl.mVirtualDisplayIds.add(1);
-        mDeviceImpl.mVirtualDisplayIds.add(2);
-        mDeviceImpl.mVirtualDisplayIds.add(3);
+        addVirtualDisplay(mDeviceImpl, 1);
+        addVirtualDisplay(mDeviceImpl, 2);
+        addVirtualDisplay(mDeviceImpl, 3);
         VirtualMouseConfig config1 = new VirtualMouseConfig.Builder()
                 .setAssociatedDisplayId(1)
                 .setInputDeviceName(DEVICE_NAME_1)
@@ -1346,7 +1347,9 @@
         mDeviceImpl.createVirtualMouse(config1, BINDER);
         mDeviceImpl.createVirtualMouse(config2, BINDER);
         mDeviceImpl.createVirtualMouse(config3, BINDER);
+        clearInvocations(mInputManagerInternalMock);
         mDeviceImpl.setShowPointerIcon(false);
+
         verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(false), anyInt());
         verify(mInputManagerInternalMock, never()).setPointerIconVisible(eq(true), anyInt());
         mDeviceImpl.setShowPointerIcon(true);
@@ -1355,9 +1358,8 @@
 
     @Test
     public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1376,9 +1378,8 @@
 
     @Test
     public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1397,9 +1398,8 @@
 
     @Test
     public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1418,9 +1418,8 @@
 
     @Test
     public void openVendingOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1439,9 +1438,8 @@
 
     @Test
     public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1460,9 +1458,8 @@
 
     @Test
     public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() {
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
@@ -1482,9 +1479,8 @@
     @Test
     public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
 
         gwpc.onRunningAppsChanged(uids);
@@ -1497,11 +1493,10 @@
     @Test
     public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+        gwpc.unregisterRunningAppsChangedListener(mDeviceImpl);
 
         // This call should not throw any exceptions.
         gwpc.onRunningAppsChanged(uids);
@@ -1512,9 +1507,8 @@
     @Test
     public void canActivityBeLaunched_activityCanLaunch() {
         Intent intent = new Intent(ACTION_VIEW, Uri.parse(TEST_SITE));
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
@@ -1537,9 +1531,8 @@
         doReturn(interceptor).when(interceptor).asBinder();
         doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
 
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
@@ -1581,9 +1574,8 @@
         doReturn(interceptor).when(interceptor).asBinder();
         doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
 
-        mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
                 DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
@@ -1626,8 +1618,7 @@
     }
 
     @Test
-    public void
-            restrictedActivityOnNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
+    public void restrictedActivityNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
         Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "def");
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1654,15 +1645,15 @@
 
     @Test
     public void getDisplayIdsForDevice_oneDisplay_resultContainsCorrectDisplayId() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
         assertThat(displayIds).containsExactly(DISPLAY_ID_1);
     }
 
     @Test
     public void getDisplayIdsForDevice_twoDisplays_resultContainsCorrectDisplayIds() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_2);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_2);
         ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
         assertThat(displayIds).containsExactly(DISPLAY_ID_1, DISPLAY_ID_2);
     }
@@ -1677,15 +1668,22 @@
     private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid,
             VirtualDeviceParams params) {
         VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
-                mInputController, mSensorController, mCameraAccessController,
-                /* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId),
+                mAssociationInfo, mVdms, new Binder(), ownerUid, virtualDeviceId,
+                mInputController, mSensorController, mCameraAccessController
+                /* onDeviceCloseListener= */ /*deviceId -> mVdms.removeVirtualDevice(deviceId)*/,
                 mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
-                mRunningAppsChangedCallback, params);
+                mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager));
         mVdms.addVirtualDevice(virtualDeviceImpl);
         return virtualDeviceImpl;
     }
 
+    private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId) {
+        when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback),
+                eq(virtualDevice), any(), any())).thenReturn(displayId);
+        virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback,
+                NONBLOCKED_APP_PACKAGE_NAME);
+    }
+
     /** Helper class to drop permissions temporarily and restore them at the end of a test. */
     static final class DropShellPermissionsTemporarily implements AutoCloseable {
         DropShellPermissionsTemporarily() {
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 34540c3..b2bfd2b 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -167,8 +167,8 @@
         Mockito.`when`(context.packageManager).thenReturn(packageManager)
 
         val info = createMockReceiver()
-        Mockito.`when`(packageManager.queryBroadcastReceivers(Mockito.any(), Mockito.anyInt()))
-            .thenReturn(listOf(info))
+        Mockito.`when`(packageManager.queryBroadcastReceiversAsUser(Mockito.any(), Mockito.anyInt(),
+                Mockito.anyInt())).thenReturn(listOf(info))
         Mockito.`when`(packageManager.getReceiverInfo(Mockito.any(), Mockito.anyInt()))
             .thenReturn(info.activityInfo)
 
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
index fb1a8f8..c0a994b 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
@@ -40,6 +40,9 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.List;
 
@@ -49,7 +52,8 @@
     private static final int CONTEXT_HUB_ID = 3;
     private static final String CONTEXT_HUB_STRING = "Context Hub Info Test";
 
-    private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    private final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
     @Mock private IContextHubWrapper mMockContextHubWrapper;
     @Mock private ContextHubInfo mMockContextHubInfo;
     @Rule public final MockitoRule mockito = MockitoJUnit.rule();
@@ -62,20 +66,29 @@
         when(mMockContextHubInfo.toString()).thenReturn(CONTEXT_HUB_STRING);
         when(mMockContextHubWrapper.getHubs()).thenReturn(hubInfo);
 
-        when(mMockContextHubWrapper.supportsLocationSettingNotifications())
-                .thenReturn(true);
+        when(mMockContextHubWrapper.supportsLocationSettingNotifications()).thenReturn(true);
         when(mMockContextHubWrapper.supportsWifiSettingNotifications()).thenReturn(true);
-        when(mMockContextHubWrapper.supportsAirplaneModeSettingNotifications())
-                .thenReturn(true);
-        when(mMockContextHubWrapper.supportsMicrophoneSettingNotifications())
-                .thenReturn(true);
+        when(mMockContextHubWrapper.supportsAirplaneModeSettingNotifications()).thenReturn(true);
+        when(mMockContextHubWrapper.supportsMicrophoneSettingNotifications()).thenReturn(true);
         when(mMockContextHubWrapper.supportsBtSettingNotifications()).thenReturn(true);
     }
 
-// TODO (b/254290317): These existing tests are to setup the testing infra for the ContextHub
-//                     service and verify the constructor correctly registers a context hub.
-//                     We need to augment these tests to cover the full behavior of the
-//                     ContextHub service
+    @Test
+    public void testDump_emptyPreloadedNanoappList() {
+        when(mMockContextHubWrapper.getPreloadedNanoappIds()).thenReturn(null);
+        StringWriter stringWriter = new StringWriter();
+
+        ContextHubService service = new ContextHubService(mContext, mMockContextHubWrapper);
+        service.dump(
+                new FileDescriptor(), new PrintWriter(stringWriter), /* args= */ new String[0]);
+
+        assertThat(stringWriter.toString()).isNotEmpty();
+    }
+
+    // TODO (b/254290317): These existing tests are to setup the testing infra for the ContextHub
+    //                     service and verify the constructor correctly registers a context hub.
+    //                     We need to augment these tests to cover the full behavior of the
+    //                     ContextHub service
 
     @Test
     public void testConstructorRegistersContextHub() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 9fc46c5..4a2e5d7 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -86,6 +86,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
 import android.text.format.DateUtils;
+import android.util.Pair;
 import android.util.Range;
 
 import com.android.internal.app.ChooserActivity;
@@ -186,6 +187,7 @@
     private ShortcutInfo mShortcutInfo;
     private TestInjector mInjector;
     private TestLooper mLooper;
+    private TestPerPackageThrottler mShortcutThrottler;
 
     @Before
     public void setUp() throws PackageManager.NameNotFoundException {
@@ -275,7 +277,9 @@
 
         mInjector = new TestInjector();
         mLooper = new TestLooper();
-        mDataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
+        mShortcutThrottler = new TestPerPackageThrottler();
+        mDataManager = new DataManager(mContext, mInjector, mLooper.getLooper(),
+                mShortcutThrottler);
         mDataManager.initialize();
 
         when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
@@ -283,10 +287,7 @@
 
         mShortcutInfo = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
                 buildPerson());
-        when(mShortcutServiceInternal.getShortcuts(
-                anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
-                anyInt(), anyInt(), anyInt(), anyInt()))
-                .thenReturn(Collections.singletonList(mShortcutInfo));
+        mockGetShortcuts(Collections.singletonList(mShortcutInfo));
         verify(mShortcutServiceInternal).addShortcutChangeCallback(
                 mShortcutChangeCallbackCaptor.capture());
         mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
@@ -972,6 +973,7 @@
                 buildPerson());
         ShortcutInfo shortcut3 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc3",
                 buildPerson());
+        mockGetShortcuts(List.of(shortcut1, shortcut2, shortcut3));
         mShortcutChangeCallback.onShortcutsAddedOrUpdated(TEST_PKG_NAME,
                 Arrays.asList(shortcut1, shortcut2, shortcut3), UserHandle.of(USER_ID_PRIMARY));
         mShortcutChangeCallback.onShortcutsRemoved(TEST_PKG_NAME,
@@ -1223,7 +1225,6 @@
                     eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
         }
     }
-
     @Test
     public void testUncacheOldestCachedShortcut_missingNotificationEvents() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
@@ -1233,6 +1234,7 @@
             ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
                     buildPerson());
             shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+            mockGetShortcuts(Collections.singletonList(shortcut));
             mShortcutChangeCallback.onShortcutsAddedOrUpdated(
                     TEST_PKG_NAME,
                     Collections.singletonList(shortcut),
@@ -1252,7 +1254,6 @@
                     eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
         }
     }
-
     @Test
     public void testUncacheOldestCachedShortcut_legacyConversation() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
@@ -1274,6 +1275,7 @@
             ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
                     buildPerson());
             shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+            mockGetShortcuts(Collections.singletonList(shortcut));
             mShortcutChangeCallback.onShortcutsAddedOrUpdated(
                     TEST_PKG_NAME,
                     Collections.singletonList(shortcut),
@@ -1311,7 +1313,8 @@
         mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
         byte[] payload = mDataManager.getBackupPayload(USER_ID_PRIMARY);
 
-        DataManager dataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
+        DataManager dataManager = new DataManager(
+                mContext, mInjector, mLooper.getLooper(), mShortcutThrottler);
         dataManager.onUserUnlocked(USER_ID_PRIMARY);
         dataManager.restore(USER_ID_PRIMARY, payload);
         ConversationInfo conversationInfo = dataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
@@ -1723,6 +1726,13 @@
         return (queryFlags & flag) != 0;
     }
 
+    private void mockGetShortcuts(List<ShortcutInfo> shortcutInfoList) {
+        when(mShortcutServiceInternal.getShortcuts(
+                anyInt(), anyString(), anyLong(), anyString(), any(), any(), any(),
+                anyInt(), anyInt(), anyInt(), anyInt()))
+                .thenReturn(shortcutInfoList);
+    }
+
     // "Sends" a notification to a non-customized notification channel - the notification channel
     // is something generic like "messages" and the notification has a  shortcut id
     private void sendGenericNotification() {
@@ -1984,4 +1994,11 @@
             return mUsageStatsQueryHelper;
         }
     }
+
+    private static class TestPerPackageThrottler implements PerPackageThrottler {
+        @Override
+        public void scheduleDebounced(Pair<String, Integer> pkgUserId, Runnable runnable) {
+            runnable.run();
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/people/data/PerPackageThrottlerImplTest.java b/services/tests/servicestests/src/com/android/server/people/data/PerPackageThrottlerImplTest.java
new file mode 100644
index 0000000..672cbb9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/people/data/PerPackageThrottlerImplTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.people.data;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.util.Pair;
+
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@RunWith(JUnit4.class)
+public class PerPackageThrottlerImplTest {
+    private static final int DEBOUNCE_INTERVAL = 500;
+    private static final String PKG_ONE = "pkg_one";
+    private static final String PKG_TWO = "pkg_two";
+    private static final int USER_ID = 10;
+
+    private final OffsettableClock mClock = new OffsettableClock.Stopped();
+    private final TestHandler mTestHandler = new TestHandler(null, mClock);
+    private PerPackageThrottlerImpl mThrottler;
+
+    @Before
+    public void setUp() {
+        mThrottler = new PerPackageThrottlerImpl(mTestHandler, DEBOUNCE_INTERVAL);
+    }
+
+    @Test
+    public void scheduleDebounced() {
+        AtomicBoolean pkgOneRan = new AtomicBoolean();
+        AtomicBoolean pkgTwoRan = new AtomicBoolean();
+
+        mThrottler.scheduleDebounced(new Pair<>(PKG_ONE, USER_ID), () -> pkgOneRan.set(true));
+        mThrottler.scheduleDebounced(new Pair<>(PKG_ONE, USER_ID), () -> pkgOneRan.set(true));
+        mThrottler.scheduleDebounced(new Pair<>(PKG_TWO, USER_ID), () -> pkgTwoRan.set(true));
+        mThrottler.scheduleDebounced(new Pair<>(PKG_TWO, USER_ID), () -> pkgTwoRan.set(true));
+
+        assertFalse(pkgOneRan.get());
+        assertFalse(pkgTwoRan.get());
+        mClock.fastForward(DEBOUNCE_INTERVAL);
+        mTestHandler.timeAdvance();
+        assertTrue(pkgOneRan.get());
+        assertTrue(pkgTwoRan.get());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java
index 2f431bd..d91ee92 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java
@@ -111,12 +111,11 @@
         verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
 
         // Check everything happened that was supposed to.
+        NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult);
+        verify(mockCallback).submitSuggestion(expectedSuggestion);
         long expectedDelayMillis = normalPollingIntervalMillis;
         verify(mockCallback).scheduleNextRefresh(
                 timeResult.getElapsedRealtimeMillis() + expectedDelayMillis);
-
-        NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult);
-        verify(mockCallback).submitSuggestion(expectedSuggestion);
     }
 
     @Test
@@ -148,6 +147,7 @@
             verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
 
             // Check everything happened that was supposed to.
+            verify(mockCallback, never()).submitSuggestion(any());
             long expectedDelayMillis;
             if (i < tryAgainTimesMax) {
                 expectedDelayMillis = shortPollingIntervalMillis;
@@ -156,7 +156,6 @@
             }
             verify(mockCallback).scheduleNextRefresh(
                     mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
-            verify(mockCallback, never()).submitSuggestion(any());
 
             reset(mMockNtpTrustedTime);
         }
@@ -196,10 +195,10 @@
             // initially.
             verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
 
+            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
             long expectedDelayMillis = normalPollingIntervalMillis;
             verify(mockCallback).scheduleNextRefresh(
                     timeResult.getElapsedRealtimeMillis() + expectedDelayMillis);
-            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
             reset(mMockNtpTrustedTime);
         }
 
@@ -222,6 +221,9 @@
             // Expect a refresh attempt each time as the cached network time is too old.
             verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
 
+            // No valid time, no suggestion.
+            verify(mockCallback, never()).submitSuggestion(any());
+
             // Check the scheduling.
             long expectedDelayMillis;
             if (i < tryAgainTimesMax) {
@@ -232,9 +234,6 @@
             verify(mockCallback).scheduleNextRefresh(
                     mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
 
-            // No valid time, no suggestion.
-            verify(mockCallback, never()).submitSuggestion(any());
-
             reset(mMockNtpTrustedTime);
 
             // Simulate the passage of time for realism.
@@ -276,10 +275,10 @@
             // initially.
             verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
 
+            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
             long expectedDelayMillis = normalPollingIntervalMillis;
             verify(mockCallback).scheduleNextRefresh(
                     timeResult.getElapsedRealtimeMillis() + expectedDelayMillis);
-            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
             reset(mMockNtpTrustedTime);
         }
 
@@ -302,15 +301,14 @@
             // Expect a refresh attempt each time as the cached network time is too old.
             verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
 
-            // Check the scheduling. tryAgainTimesMax == 0, so the algorithm should start with
+            // No valid time, no suggestion.
+            verify(mockCallback, never()).submitSuggestion(any());
 
+            // Check the scheduling. tryAgainTimesMax == 0, so the algorithm should start with
             long expectedDelayMillis = normalPollingIntervalMillis;
             verify(mockCallback).scheduleNextRefresh(
                     mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
 
-            // No valid time, no suggestion.
-            verify(mockCallback, never()).submitSuggestion(any());
-
             reset(mMockNtpTrustedTime);
 
             // Simulate the passage of time for realism.
@@ -352,10 +350,10 @@
             // initially.
             verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
 
+            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
             long expectedDelayMillis = normalPollingIntervalMillis;
             verify(mockCallback).scheduleNextRefresh(
                     timeResult.getElapsedRealtimeMillis() + expectedDelayMillis);
-            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
             reset(mMockNtpTrustedTime);
         }
 
@@ -378,15 +376,15 @@
             // Expect a refresh attempt each time as the cached network time is too old.
             verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
 
+            // No valid time, no suggestion.
+            verify(mockCallback, never()).submitSuggestion(any());
+
             // Check the scheduling. tryAgainTimesMax == -1, so it should always be
             // shortPollingIntervalMillis.
             long expectedDelayMillis = shortPollingIntervalMillis;
             verify(mockCallback).scheduleNextRefresh(
                     mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
 
-            // No valid time, no suggestion.
-            verify(mockCallback, never()).submitSuggestion(any());
-
             reset(mMockNtpTrustedTime);
 
             // Simulate the passage of time for realism.
@@ -426,11 +424,11 @@
             // initially.
             verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
 
+            NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult1);
+            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
             long expectedDelayMillis = normalPollingIntervalMillis;
             verify(mockCallback).scheduleNextRefresh(
                     timeResult1.getElapsedRealtimeMillis() + expectedDelayMillis);
-            NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult1);
-            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
             reset(mMockNtpTrustedTime);
         }
 
@@ -453,12 +451,13 @@
             // Expect the refresh attempt to have been made: the timeResult is too old.
             verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
 
+            // No valid time, no suggestion.
+            verify(mockCallback, never()).submitSuggestion(any());
+
             long expectedDelayMillis = shortPollingIntervalMillis;
             verify(mockCallback).scheduleNextRefresh(
                     mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
 
-            // No valid time, no suggestion.
-            verify(mockCallback, never()).submitSuggestion(any());
             reset(mMockNtpTrustedTime);
         }
 
@@ -485,11 +484,11 @@
             // Expect the refresh attempt to have been made: the timeResult is too old.
             verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
 
+            NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult2);
+            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
             long expectedDelayMillis = normalPollingIntervalMillis;
             verify(mockCallback).scheduleNextRefresh(
                     timeResult2.getElapsedRealtimeMillis() + expectedDelayMillis);
-            NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult2);
-            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
             reset(mMockNtpTrustedTime);
         }
     }
@@ -525,16 +524,16 @@
         // Expect no refresh attempt to have been made.
         verify(mMockNtpTrustedTime, never()).forceRefresh(any());
 
+        // Suggestions must be made every time if the cached time value is not too old in case it
+        // was refreshed by a different component.
+        NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult);
+        verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
+
         // The next wake-up should be rescheduled for when the cached time value will become too
         // old.
         long expectedDelayMillis = normalPollingIntervalMillis;
         verify(mockCallback).scheduleNextRefresh(
                 timeResult.getElapsedRealtimeMillis() + expectedDelayMillis);
-
-        // Suggestions must be made every time if the cached time value is not too old in case it
-        // was refreshed by a different component.
-        NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult);
-        verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
     }
 
     /**
@@ -568,13 +567,13 @@
         // Expect a refresh attempt to have been made.
         verify(mMockNtpTrustedTime, times(1)).forceRefresh(mDummyNetwork);
 
+        // Suggestions should not be made if the cached time value is too old.
+        verify(mockCallback, never()).submitSuggestion(any());
+
         // The next wake-up should be rescheduled using the short polling interval.
         long expectedDelayMillis = shortPollingIntervalMillis;
         verify(mockCallback).scheduleNextRefresh(
                 mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
-
-        // Suggestions should not be made if the cached time value is too old.
-        verify(mockCallback, never()).submitSuggestion(any());
     }
 
     /**
@@ -614,6 +613,9 @@
             verify(mMockNtpTrustedTime, times(1)).forceRefresh(mDummyNetwork);
             lastRefreshAttemptElapsedMillis = mFakeElapsedRealtimeClock.getElapsedRealtimeMillis();
 
+            // Suggestions should not be made if the cached time value is too old.
+            verify(mockCallback, never()).submitSuggestion(any());
+
             // The next wake-up should be rescheduled using the normalPollingIntervalMillis.
             // Because the time signal age > normalPollingIntervalMillis, the last refresh attempt
             // time will be used.
@@ -622,9 +624,6 @@
                     lastRefreshAttemptElapsedMillis + expectedDelayMillis;
             verify(mockCallback).scheduleNextRefresh(expectedNextRefreshElapsedMillis);
 
-            // Suggestions should not be made if the cached time value is too old.
-            verify(mockCallback, never()).submitSuggestion(any());
-
             reset(mMockNtpTrustedTime);
         }
 
@@ -646,6 +645,9 @@
             // Expect no refresh attempt to have been made: time elapsed isn't enough.
             verify(mMockNtpTrustedTime, never()).forceRefresh(any());
 
+            // Suggestions should not be made if the cached time value is too old.
+            verify(mockCallback, never()).submitSuggestion(any());
+
             // The next wake-up should be rescheduled using the normal polling interval and the last
             // refresh attempt time.
             long expectedDelayMillis = normalPollingIntervalMillis;
@@ -653,11 +655,110 @@
                     lastRefreshAttemptElapsedMillis + expectedDelayMillis;
             verify(mockCallback).scheduleNextRefresh(expectedNextRefreshElapsedMillis);
 
+            reset(mMockNtpTrustedTime);
+        }
+    }
+
+    /**
+     * Confirms that if a refreshAndRescheduleIfRequired() call is made and there was a recently
+     * failed refresh, then another won't be scheduled too soon.
+     */
+    @Test
+    public void engineImpl_refreshAndRescheduleIfRequired_minimumRefreshTimeEnforced_b269425914() {
+        mFakeElapsedRealtimeClock.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS);
+
+        int normalPollingIntervalMillis = 7777777;
+        int shortPollingIntervalMillis = 3333;
+        int tryAgainTimesMax = 3;
+        NetworkTimeUpdateService.Engine engine = new NetworkTimeUpdateService.EngineImpl(
+                mFakeElapsedRealtimeClock,
+                normalPollingIntervalMillis, shortPollingIntervalMillis, tryAgainTimesMax,
+                mMockNtpTrustedTime);
+
+        NtpTrustedTime.TimeResult timeResult1;
+
+        // Start out with a successful refresh.
+        long lastRefreshAttemptElapsedMillis;
+        {
+            mFakeElapsedRealtimeClock.incrementMillis(normalPollingIntervalMillis);
+            timeResult1 = createNtpTimeResult(mFakeElapsedRealtimeClock.getElapsedRealtimeMillis());
+            when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null, timeResult1);
+            when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(true);
+
+            RefreshCallbacks mockCallback = mock(RefreshCallbacks.class);
+            // Trigger the engine's logic.
+            engine.refreshAndRescheduleIfRequired(mDummyNetwork, "Test", mockCallback);
+
+            // Expect a refresh attempt to have been made.
+            verify(mMockNtpTrustedTime, times(1)).forceRefresh(mDummyNetwork);
+            lastRefreshAttemptElapsedMillis = mFakeElapsedRealtimeClock.getElapsedRealtimeMillis();
+
+            NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult1);
+            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
+
+            // The next wake-up should be rescheduled using the normalPollingIntervalMillis.
+            // Because the time signal age > normalPollingIntervalMillis, the last refresh attempt
+            // time will be used.
+            long expectedDelayMillis = normalPollingIntervalMillis;
+            long expectedNextRefreshElapsedMillis =
+                    lastRefreshAttemptElapsedMillis + expectedDelayMillis;
+            verify(mockCallback).scheduleNextRefresh(expectedNextRefreshElapsedMillis);
+
+            reset(mMockNtpTrustedTime);
+        }
+
+        // Now fail: This should result in the next refresh using a short polling interval.
+        {
+            mFakeElapsedRealtimeClock.incrementMillis(normalPollingIntervalMillis);
+            when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(timeResult1);
+            when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(false);
+
+            RefreshCallbacks mockCallback = mock(RefreshCallbacks.class);
+            // Trigger the engine's logic.
+            engine.refreshAndRescheduleIfRequired(mDummyNetwork, "Test", mockCallback);
+
+            verify(mMockNtpTrustedTime, times(1)).forceRefresh(mDummyNetwork);
+            lastRefreshAttemptElapsedMillis = mFakeElapsedRealtimeClock.getElapsedRealtimeMillis();
+
             // Suggestions should not be made if the cached time value is too old.
             verify(mockCallback, never()).submitSuggestion(any());
 
+            // The next wake-up should be rescheduled using the last refresh attempt time (because
+            // the latest time result is too old) and the short polling interval.
+            long expectedDelayMillis = shortPollingIntervalMillis;
+            long expectedNextRefreshElapsedMillis =
+                    lastRefreshAttemptElapsedMillis + expectedDelayMillis;
+            verify(mockCallback).scheduleNextRefresh(expectedNextRefreshElapsedMillis);
+
             reset(mMockNtpTrustedTime);
         }
+
+        // Simulate some other thread successfully refreshing the value held by NtpTrustedTime and
+        // confirm it is handled correctly.
+        {
+            mFakeElapsedRealtimeClock.incrementMillis(shortPollingIntervalMillis / 2);
+            NtpTrustedTime.TimeResult timeResult2 = createNtpTimeResult(
+                    mFakeElapsedRealtimeClock.getElapsedRealtimeMillis());
+            when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(timeResult2);
+
+            mFakeElapsedRealtimeClock.incrementMillis(shortPollingIntervalMillis / 2);
+
+            RefreshCallbacks mockCallback = mock(RefreshCallbacks.class);
+            // Trigger the engine's logic.
+            engine.refreshAndRescheduleIfRequired(mDummyNetwork, "Test", mockCallback);
+
+            verify(mMockNtpTrustedTime, never()).forceRefresh(any());
+
+            NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult2);
+            verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
+
+            // The next wake-up should be rescheduled using the normal polling interval and the
+            // latest time result.
+            long expectedDelayMillis = normalPollingIntervalMillis;
+            long expectedNextRefreshElapsedMillis =
+                    timeResult2.getElapsedRealtimeMillis() + expectedDelayMillis;
+            verify(mockCallback).scheduleNextRefresh(expectedNextRefreshElapsedMillis);
+        }
     }
 
     private static NetworkTimeSuggestion createExpectedSuggestion(
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 5a0867f..daa6823 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -386,16 +386,16 @@
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
 
         assertThrows(SecurityException.class,
-                () -> mTimeDetectorService.clearNetworkTime());
+                () -> mTimeDetectorService.clearLatestNetworkTime());
         verify(mMockContext).enforceCallingPermission(
                 eq(android.Manifest.permission.SET_TIME), anyString());
     }
 
     @Test
-    public void testClearNetworkTime() throws Exception {
+    public void testClearLatestNetworkSuggestion() throws Exception {
         doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        mTimeDetectorService.clearNetworkTime();
+        mTimeDetectorService.clearLatestNetworkTime();
 
         verify(mMockContext).enforceCallingPermission(
                 eq(android.Manifest.permission.SET_TIME), anyString());
@@ -403,53 +403,48 @@
     }
 
     @Test
-    public void testLatestNetworkTime() {
-        NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
-                1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
-        when(mMockNtpTrustedTime.getCachedTimeResult())
-                .thenReturn(latestNetworkTime);
-        UnixEpochTime expected = new UnixEpochTime(
-                latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
-        assertEquals(expected, mTimeDetectorService.latestNetworkTime());
-    }
-
-    @Test
-    public void testLatestNetworkTime_noTimeAvailable() {
-        when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
-        assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime());
-    }
-
-    @Test
     public void testGetLatestNetworkSuggestion() {
-        if (TimeDetectorNetworkTimeHelper.isInUse()) {
-            NetworkTimeSuggestion latestNetworkTime = createNetworkTimeSuggestion();
-            mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkTime);
+        NetworkTimeSuggestion latestNetworkSuggestion = createNetworkTimeSuggestion();
+        mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkSuggestion);
 
-            assertEquals(latestNetworkTime, mTimeDetectorService.getLatestNetworkSuggestion());
+        assertEquals(latestNetworkSuggestion, mTimeDetectorService.getLatestNetworkSuggestion());
+    }
+
+    @Test
+    public void testGetLatestNetworkSuggestion_noTimeAvailable() {
+        mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null);
+
+        assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+    }
+
+    @Test
+    public void testLatestNetworkTime() {
+        if (TimeDetectorNetworkTimeHelper.isInUse()) {
+            NetworkTimeSuggestion latestNetworkSuggestion = createNetworkTimeSuggestion();
+            mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkSuggestion);
+
+            assertEquals(latestNetworkSuggestion.getUnixEpochTime(),
+                    mTimeDetectorService.latestNetworkTime());
         } else {
             NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
                     1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
             when(mMockNtpTrustedTime.getCachedTimeResult())
                     .thenReturn(latestNetworkTime);
-            UnixEpochTime expectedUnixEpochTime = new UnixEpochTime(
+            UnixEpochTime expected = new UnixEpochTime(
                     latestNetworkTime.getElapsedRealtimeMillis(),
                     latestNetworkTime.getTimeMillis());
-            NetworkTimeSuggestion expected = new NetworkTimeSuggestion(
-                    expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis());
-            assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion());
+            assertEquals(expected, mTimeDetectorService.latestNetworkTime());
         }
     }
 
     @Test
-    public void testGetLatestNetworkSuggestion_noTimeAvailable() {
+    public void testLatestNetworkTime_noTimeAvailable() {
         if (TimeDetectorNetworkTimeHelper.isInUse()) {
             mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null);
-
-            assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
         } else {
             when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
-            assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
         }
+        assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime());
     }
 
     @Test
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index b921838..4c0361d 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -263,7 +263,7 @@
                 + instrumentation.getContext().getUserId() + " " + RoleManager.ROLE_HOME + " "
                 + packageName + " 0");
         waitUntil("Failed to get shortcut access",
-                () -> hasShortcutAccess(instrumentation, packageName), 20);
+                () -> hasShortcutAccess(instrumentation, packageName), 60);
     }
 
     public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 2b6db14..893f538 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -32,9 +32,9 @@
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.EventInfo;
 import android.service.notification.ZenPolicy;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Xml;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -43,11 +43,13 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -189,8 +191,6 @@
 
     @Test
     public void testRuleXml() throws Exception {
-        String tag = "tag";
-
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
         rule.configurationActivity = new ComponentName("a", "a");
         rule.component = new ComponentName("b", "b");
@@ -205,20 +205,11 @@
         rule.snoozing = true;
         rule.pkg = "b";
 
-        TypedXmlSerializer out = Xml.newFastSerializer();
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        out.setOutput(new BufferedOutputStream(baos), "utf-8");
-        out.startDocument(null, true);
-        out.startTag(null, tag);
-        ZenModeConfig.writeRuleXml(rule, out);
-        out.endTag(null, tag);
-        out.endDocument();
+        writeRuleXml(rule, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
 
-        TypedXmlPullParser parser = Xml.newFastPullParser();
-        parser.setInput(new BufferedInputStream(
-                new ByteArrayInputStream(baos.toByteArray())), null);
-        parser.nextTag();
-        ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser);
         assertEquals("b", fromXml.pkg);
         // always resets on reboot
         assertFalse(fromXml.snoozing);
@@ -237,75 +228,41 @@
 
     @Test
     public void testRuleXml_pkg_component() throws Exception {
-        String tag = "tag";
-
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
         rule.configurationActivity = new ComponentName("a", "a");
         rule.component = new ComponentName("b", "b");
 
-        TypedXmlSerializer out = Xml.newFastSerializer();
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        out.setOutput(new BufferedOutputStream(baos), "utf-8");
-        out.startDocument(null, true);
-        out.startTag(null, tag);
-        ZenModeConfig.writeRuleXml(rule, out);
-        out.endTag(null, tag);
-        out.endDocument();
+        writeRuleXml(rule, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
 
-        TypedXmlPullParser parser = Xml.newFastPullParser();
-        parser.setInput(new BufferedInputStream(
-                new ByteArrayInputStream(baos.toByteArray())), null);
-        parser.nextTag();
-        ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser);
         assertEquals("b", fromXml.pkg);
     }
 
     @Test
     public void testRuleXml_pkg_configActivity() throws Exception {
-        String tag = "tag";
-
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
         rule.configurationActivity = new ComponentName("a", "a");
 
-        TypedXmlSerializer out = Xml.newFastSerializer();
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        out.setOutput(new BufferedOutputStream(baos), "utf-8");
-        out.startDocument(null, true);
-        out.startTag(null, tag);
-        ZenModeConfig.writeRuleXml(rule, out);
-        out.endTag(null, tag);
-        out.endDocument();
+        writeRuleXml(rule, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
 
-        TypedXmlPullParser parser = Xml.newFastPullParser();
-        parser.setInput(new BufferedInputStream(
-                new ByteArrayInputStream(baos.toByteArray())), null);
-        parser.nextTag();
-        ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser);
         assertNull(fromXml.pkg);
     }
 
     @Test
     public void testRuleXml_getPkg_nullPkg() throws Exception {
-        String tag = "tag";
-
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
         rule.enabled = true;
         rule.configurationActivity = new ComponentName("a", "a");
 
-        TypedXmlSerializer out = Xml.newFastSerializer();
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        out.setOutput(new BufferedOutputStream(baos), "utf-8");
-        out.startDocument(null, true);
-        out.startTag(null, tag);
-        ZenModeConfig.writeRuleXml(rule, out);
-        out.endTag(null, tag);
-        out.endDocument();
-
-        TypedXmlPullParser parser = Xml.newFastPullParser();
-        parser.setInput(new BufferedInputStream(
-                new ByteArrayInputStream(baos.toByteArray())), null);
-        parser.nextTag();
-        ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser);
+        writeRuleXml(rule, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
         assertEquals("a", fromXml.getPkg());
 
         fromXml.condition = new Condition(Uri.EMPTY, "", Condition.STATE_TRUE);
@@ -313,25 +270,26 @@
     }
 
     @Test
-    public void testZenPolicyXml_allUnset() throws Exception {
-        String tag = "tag";
+    public void testRuleXml_emptyConditionId() throws Exception {
+        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+        rule.conditionId = Uri.EMPTY;
 
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        writeRuleXml(rule, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
+
+        assertEquals(rule.condition, fromXml.condition);
+    }
+
+    @Test
+    public void testZenPolicyXml_allUnset() throws Exception {
         ZenPolicy policy = new ZenPolicy.Builder().build();
 
-        TypedXmlSerializer out = Xml.newFastSerializer();
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        out.setOutput(new BufferedOutputStream(baos), "utf-8");
-        out.startDocument(null, true);
-        out.startTag(null, tag);
-        ZenModeConfig.writeZenPolicyXml(policy, out);
-        out.endTag(null, tag);
-        out.endDocument();
-
-        TypedXmlPullParser parser = Xml.newFastPullParser();
-        parser.setInput(new BufferedInputStream(
-                new ByteArrayInputStream(baos.toByteArray())), null);
-        parser.nextTag();
-        ZenPolicy fromXml = ZenModeConfig.readZenPolicyXml(parser);
+        writePolicyXml(policy, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenPolicy fromXml = readPolicyXml(bais);
 
         // nothing was set, so we should have nothing from the parser
         assertNull(fromXml);
@@ -339,8 +297,6 @@
 
     @Test
     public void testZenPolicyXml() throws Exception {
-        String tag = "tag";
-
         ZenPolicy policy = new ZenPolicy.Builder()
                 .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
                 .allowMessages(ZenPolicy.PEOPLE_TYPE_NONE)
@@ -355,20 +311,10 @@
                 .showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true)
                 .build();
 
-        TypedXmlSerializer out = Xml.newFastSerializer();
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        out.setOutput(new BufferedOutputStream(baos), "utf-8");
-        out.startDocument(null, true);
-        out.startTag(null, tag);
-        ZenModeConfig.writeZenPolicyXml(policy, out);
-        out.endTag(null, tag);
-        out.endDocument();
-
-        TypedXmlPullParser parser = Xml.newFastPullParser();
-        parser.setInput(new BufferedInputStream(
-                new ByteArrayInputStream(baos.toByteArray())), null);
-        parser.nextTag();
-        ZenPolicy fromXml = ZenModeConfig.readZenPolicyXml(parser);
+        writePolicyXml(policy, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenPolicy fromXml = readPolicyXml(bais);
 
         assertNotNull(fromXml);
         assertEquals(policy.getPriorityCategoryCalls(), fromXml.getPriorityCategoryCalls());
@@ -457,4 +403,45 @@
         config.suppressedVisualEffects = 0;
         return config;
     }
+
+    private void writeRuleXml(ZenModeConfig.ZenRule rule, ByteArrayOutputStream os)
+            throws IOException {
+        String tag = "tag";
+
+        TypedXmlSerializer out = Xml.newFastSerializer();
+        out.setOutput(new BufferedOutputStream(os), "utf-8");
+        out.startDocument(null, true);
+        out.startTag(null, tag);
+        ZenModeConfig.writeRuleXml(rule, out);
+        out.endTag(null, tag);
+        out.endDocument();
+    }
+
+    private ZenModeConfig.ZenRule readRuleXml(ByteArrayInputStream is)
+            throws XmlPullParserException, IOException {
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(is), null);
+        parser.nextTag();
+        return ZenModeConfig.readRuleXml(parser);
+    }
+
+    private void writePolicyXml(ZenPolicy policy, ByteArrayOutputStream os) throws IOException {
+        String tag = "tag";
+
+        TypedXmlSerializer out = Xml.newFastSerializer();
+        out.setOutput(new BufferedOutputStream(os), "utf-8");
+        out.startDocument(null, true);
+        out.startTag(null, tag);
+        ZenModeConfig.writeZenPolicyXml(policy, out);
+        out.endTag(null, tag);
+        out.endDocument();
+    }
+
+    private ZenPolicy readPolicyXml(ByteArrayInputStream is)
+            throws XmlPullParserException, IOException {
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(is), null);
+        parser.nextTag();
+        return ZenModeConfig.readZenPolicyXml(parser);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index e5fe32a..6661e6a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2324,6 +2324,32 @@
     }
 
     @Test
+    public void testActivityServiceConnectionsHolder() {
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityServiceConnectionsHolder<Object> holder =
+                mAtm.mInternal.getServiceConnectionsHolder(activity.token);
+        assertNotNull(holder);
+        final Object connection = new Object();
+        holder.addConnection(connection);
+        assertTrue(holder.isActivityVisible());
+        final int[] count = new int[1];
+        final Consumer<Object> c = conn -> count[0]++;
+        holder.forEachConnection(c);
+        assertEquals(1, count[0]);
+
+        holder.removeConnection(connection);
+        holder.forEachConnection(c);
+        assertEquals(1, count[0]);
+
+        activity.setVisibleRequested(false);
+        activity.setState(STOPPED, "test");
+        assertFalse(holder.isActivityVisible());
+
+        activity.removeImmediately();
+        assertNull(mAtm.mInternal.getServiceConnectionsHolder(activity.token));
+    }
+
+    @Test
     public void testTransferLaunchCookieWhenFinishing() {
         final ActivityRecord activity1 = createActivityWithTask();
         final Binder launchCookie = new Binder();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 28e493e..e3cb5fb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -145,7 +145,8 @@
 public class ActivityStarterTests extends WindowTestsBase {
 
     private static final String ENABLE_DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER =
-            "enable_default_rescind_bal_privileges_from_pending_intent_sender";
+            "DefaultRescindBalPrivilegesFromPendingIntentSender__"
+                    + "enable_default_rescind_bal_privileges_from_pending_intent_sender";
     private static final int PRECONDITION_NO_CALLER_APP = 1;
     private static final int PRECONDITION_NO_INTENT_COMPONENT = 1 << 1;
     private static final int PRECONDITION_NO_ACTIVITY_INFO = 1 << 2;
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index ff5ede7..56461f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -17,8 +17,11 @@
 package com.android.server.wm;
 
 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.window.BackNavigationInfo.typeToString;
 
@@ -42,8 +45,10 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.os.Bundle;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
 import android.view.WindowManager;
 import android.window.BackAnimationAdapter;
 import android.window.BackMotionEvent;
@@ -320,6 +325,64 @@
         assertThat(backNavigationInfo).isNull();
     }
 
+    @Test
+    public void testTransitionHappensCancelNavigation() {
+        // Create a floating task and a fullscreen task, then navigating on fullscreen task.
+        // The navigation should not been cancelled when transition happens on floating task, and
+        // only be cancelled when transition happens on the navigating task.
+        final Task floatingTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
+                ACTIVITY_TYPE_STANDARD);
+        final ActivityRecord baseFloatingActivity = createActivityRecord(floatingTask);
+
+        final Task fullscreenTask = createTopTaskWithActivity();
+        withSystemCallback(fullscreenTask);
+        final ActivityRecord baseFullscreenActivity = fullscreenTask.getTopMostActivity();
+
+        final CountDownLatch navigationObserver = new CountDownLatch(1);
+        startBackNavigation(navigationObserver);
+
+        final ArraySet<ActivityRecord> opening = new ArraySet<>();
+        final ArraySet<ActivityRecord> closing = new ArraySet<>();
+        final ActivityRecord secondFloatingActivity = createActivityRecord(floatingTask);
+        opening.add(secondFloatingActivity);
+        closing.add(baseFloatingActivity);
+        mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing);
+        assertEquals("Transition happen on an irrelevant task, callback should not been called",
+                1, navigationObserver.getCount());
+
+        // Create a new activity above navigation target, the transition should cancel navigation.
+        final ActivityRecord topFullscreenActivity = createActivityRecord(fullscreenTask);
+        opening.clear();
+        closing.clear();
+        opening.add(topFullscreenActivity);
+        closing.add(baseFullscreenActivity);
+        mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing);
+        assertEquals("Transition happen on navigation task, callback should have been called",
+                0, navigationObserver.getCount());
+    }
+
+    @Test
+    public void testWindowFocusChangeCancelNavigation() {
+        Task task = createTopTaskWithActivity();
+        withSystemCallback(task);
+        WindowState focusWindow = task.getTopVisibleAppMainWindow();
+        final CountDownLatch navigationObserver = new CountDownLatch(1);
+        startBackNavigation(navigationObserver);
+
+        mBackNavigationController.onFocusChanged(null);
+        assertEquals("change focus to null, callback should not have been called",
+                1, navigationObserver.getCount());
+        mBackNavigationController.onFocusChanged(focusWindow);
+        assertEquals("change focus back, callback should not have been called",
+                1, navigationObserver.getCount());
+
+        WindowState newWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlayWindow");
+        addToWindowMap(newWindow, true);
+        mBackNavigationController.onFocusChanged(newWindow);
+        assertEquals("Focus change, callback should have been called",
+                0, navigationObserver.getCount());
+    }
+
     private IOnBackInvokedCallback withSystemCallback(Task task) {
         IOnBackInvokedCallback callback = createOnBackInvokedCallback();
         task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo(
@@ -336,7 +399,14 @@
 
     @Nullable
     private BackNavigationInfo startBackNavigation() {
-        return mBackNavigationController.startBackNavigation(null, mBackAnimationAdapter);
+        return mBackNavigationController.startBackNavigation(
+                createNavigationObserver(null), mBackAnimationAdapter);
+    }
+
+    @Nullable
+    private BackNavigationInfo startBackNavigation(CountDownLatch navigationObserverLatch) {
+        return mBackNavigationController.startBackNavigation(
+                createNavigationObserver(navigationObserverLatch), mBackAnimationAdapter);
     }
 
     @NonNull
@@ -371,6 +441,14 @@
         };
     }
 
+    private RemoteCallback createNavigationObserver(CountDownLatch latch) {
+        return new RemoteCallback(result -> {
+            if (latch != null) {
+                latch.countDown();
+            }
+        });
+    }
+
     private Task initHomeActivity() {
         final Task task = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
         task.forAllLeafTasks((t) -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 6f633d7..06b6ed8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -68,6 +68,7 @@
 import android.view.InsetsState;
 import android.view.RoundedCorner;
 import android.view.RoundedCorners;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
@@ -353,7 +354,7 @@
     @Test
     public void testGetCropBoundsIfNeeded_noCrop() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
-                InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+                WindowInsets.Type.navigationBars());
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
 
         // Do not apply crop if taskbar is collapsed
@@ -374,7 +375,8 @@
     @Test
     public void testGetCropBoundsIfNeeded_appliesCrop() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
-                InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+                WindowInsets.Type.navigationBars());
+        taskbar.setInsetsRoundedCornerFrame(true);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
 
         // Apply crop if taskbar is expanded
@@ -396,7 +398,8 @@
     @Test
     public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
-                InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+                WindowInsets.Type.navigationBars());
+        taskbar.setInsetsRoundedCornerFrame(true);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
         final float scaling = 2.0f;
 
@@ -440,7 +443,7 @@
                     configurationRadius * 2 /*2 is to test selection of the min radius*/,
                     /*centerX=*/ 1, /*centerY=*/ 1)
         );
-        doReturn(roundedCorners).when(insets).getRoundedCorners();
+        insets.setRoundedCorners(roundedCorners);
         mLetterboxConfiguration.setLetterboxActivityCornersRadius(-1);
 
         assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
@@ -479,7 +482,7 @@
 
     private WindowState mockForGetCropBoundsAndRoundedCorners(@Nullable InsetsSource taskbar) {
         final WindowState mainWindow = mock(WindowState.class);
-        final InsetsState insets = mock(InsetsState.class);
+        final InsetsState insets = new InsetsState();
         final Resources resources = mWm.mContext.getResources();
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
 
@@ -489,7 +492,7 @@
 
         if (taskbar != null) {
             taskbar.setVisible(true);
-            doReturn(taskbar).when(insets).peekSource(taskbar.getType());
+            insets.addSource(taskbar);
         }
         doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
         doReturn(true).when(mActivity).isVisible();
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 e9aca56..d242a5f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -28,7 +28,6 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
 import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
@@ -2993,8 +2992,9 @@
         organizer.mPrimary.setBounds(0, screenHeight / 2, screenWidth, screenHeight);
         organizer.putTaskToPrimary(mTask, true);
 
-        final InsetsSource navSource =
-                new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
+        final InsetsSource navSource = new InsetsSource(
+                InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+        navSource.setInsetsRoundedCornerFrame(true);
         navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
 
         mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index ec9bd2f..06fc416 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -83,6 +83,7 @@
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.infra.AndroidFuture;
 import com.android.server.LocalServices;
+import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
 
 import java.io.Closeable;
 import java.io.IOException;
@@ -203,12 +204,16 @@
     boolean mPerformingExternalSourceHotwordDetection;
     @NonNull final IBinder mToken;
 
+    @NonNull DetectorRemoteExceptionListener mRemoteExceptionListener;
+
     DetectorSession(
             @NonNull HotwordDetectionConnection.ServiceConnection remoteDetectionService,
             @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
             @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity,
-            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging,
+            @NonNull DetectorRemoteExceptionListener listener) {
+        mRemoteExceptionListener = listener;
         mRemoteDetectionService = remoteDetectionService;
         mLock = lock;
         mContext = context;
@@ -237,6 +242,14 @@
         }
     }
 
+    void notifyOnDetectorRemoteException() {
+        Slog.d(TAG, "notifyOnDetectorRemoteException: mRemoteExceptionListener="
+                + mRemoteExceptionListener);
+        if (mRemoteExceptionListener != null) {
+            mRemoteExceptionListener.onDetectorRemoteException(mToken, getDetectorType());
+        }
+    }
+
     @SuppressWarnings("GuardedBy")
     private void updateStateAfterProcessStartLocked(PersistableBundle options,
             SharedMemory sharedMemory) {
@@ -280,6 +293,7 @@
                                     METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
                                     mVoiceInteractionServiceUid);
                         }
+                        notifyOnDetectorRemoteException();
                     }
                 }
             };
@@ -319,6 +333,7 @@
                                 METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
                                 mVoiceInteractionServiceUid);
                     }
+                    notifyOnDetectorRemoteException();
                 }
             } else if (err != null) {
                 Slog.w(TAG, "Failed to update state: " + err);
@@ -443,6 +458,7 @@
                                 HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
                                 mVoiceInteractionServiceUid);
                     }
+                    notifyOnDetectorRemoteException();
                 }
             } finally {
                 synchronized (mLock) {
@@ -479,8 +495,12 @@
                                                 EXTERNAL_HOTWORD_CLEANUP_MILLIS,
                                                 TimeUnit.MILLISECONDS);
 
-                                        callback.onRejected(result);
-
+                                        try {
+                                            callback.onRejected(result);
+                                        } catch (RemoteException e) {
+                                            notifyOnDetectorRemoteException();
+                                            throw e;
+                                        }
                                         if (result != null) {
                                             Slog.i(TAG, "Egressed 'hotword rejected result' "
                                                     + "from hotword trusted process");
@@ -510,14 +530,21 @@
                                         try {
                                             enforcePermissionsForDataDelivery();
                                         } catch (SecurityException e) {
+                                            Slog.w(TAG, "Ignoring #onDetected due to a "
+                                                    + "SecurityException", e);
                                             HotwordMetricsLogger.writeDetectorEvent(
                                                     getDetectorType(),
                                                     EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION,
                                                     mVoiceInteractionServiceUid);
-                                            callback.onError(new HotwordDetectionServiceFailure(
-                                                    CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
-                                                    "Security exception occurs in #onDetected"
-                                                            + " method."));
+                                            try {
+                                                callback.onError(new HotwordDetectionServiceFailure(
+                                                        CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
+                                                        "Security exception occurs in #onDetected"
+                                                                + " method."));
+                                            } catch (RemoteException e1) {
+                                                notifyOnDetectorRemoteException();
+                                                throw e1;
+                                            }
                                             return;
                                         }
                                         HotwordDetectedResult newResult;
@@ -525,14 +552,26 @@
                                             newResult = mHotwordAudioStreamCopier
                                                     .startCopyingAudioStreams(triggerResult);
                                         } catch (IOException e) {
+                                            Slog.w(TAG, "Ignoring #onDetected due to a "
+                                                    + "IOException", e);
                                             // TODO: Write event
-                                            callback.onError(new HotwordDetectionServiceFailure(
-                                                    CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
-                                                    "Copy audio stream failure."));
+                                            try {
+                                                callback.onError(new HotwordDetectionServiceFailure(
+                                                        CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
+                                                        "Copy audio stream failure."));
+                                            } catch (RemoteException e1) {
+                                                notifyOnDetectorRemoteException();
+                                                throw e1;
+                                            }
                                             return;
                                         }
-                                        callback.onDetected(newResult, /* audioFormat= */ null,
-                                                /* audioStream= */ null);
+                                        try {
+                                            callback.onDetected(newResult, /* audioFormat= */ null,
+                                                    /* audioStream= */ null);
+                                        } catch (RemoteException e) {
+                                            notifyOnDetectorRemoteException();
+                                            throw e;
+                                        }
                                         Slog.i(TAG, "Egressed "
                                                 + HotwordDetectedResult.getUsageSize(newResult)
                                                 + " bits from hotword trusted process");
@@ -567,6 +606,7 @@
         mDestroyed = true;
         mDebugHotwordLogging = false;
         mRemoteDetectionService = null;
+        mRemoteExceptionListener = null;
         if (mAttentionManagerInternal != null) {
             mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
         }
@@ -595,6 +635,7 @@
                         HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
                         mVoiceInteractionServiceUid);
             }
+            notifyOnDetectorRemoteException();
         }
     }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index 63e0f46..f9b5111 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -16,6 +16,7 @@
 
 package com.android.server.voiceinteraction;
 
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_DETECTED_EXCEPTION;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION;
@@ -42,6 +43,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -80,10 +82,11 @@
             @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
             @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity,
-            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging,
+            @NonNull DetectorRemoteExceptionListener listener) {
         super(remoteHotwordDetectionService, lock, context, token, callback,
                 voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
-                logging);
+                logging, listener);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -126,14 +129,23 @@
                         enforcePermissionsForDataDelivery();
                         enforceExtraKeyphraseIdNotLeaked(result, recognitionEvent);
                     } catch (SecurityException e) {
-                        Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
+                        Slog.w(TAG, "Ignoring #onDetected due to a SecurityException", e);
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
                                 METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
                                 mVoiceInteractionServiceUid);
-                        externalCallback.onDetectionFailure(new HotwordDetectionServiceFailure(
-                                CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
-                                "Security exception occurs in #onDetected method."));
+                        try {
+                            externalCallback.onDetectionFailure(new HotwordDetectionServiceFailure(
+                                    CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
+                                    "Security exception occurs in #onDetected method."));
+                        } catch (RemoteException e1) {
+                            notifyOnDetectorRemoteException();
+                            HotwordMetricsLogger.writeDetectorEvent(
+                                    HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                                    mVoiceInteractionServiceUid);
+                            throw e1;
+                        }
                         return;
                     }
                     saveProximityValueToBundle(result);
@@ -141,14 +153,29 @@
                     try {
                         newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
                     } catch (IOException e) {
-                        externalCallback.onDetectionFailure(new HotwordDetectionServiceFailure(
-                                CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
-                                "Copy audio stream failure."));
+                        try {
+                            Slog.w(TAG, "Ignoring #onDetected due to a IOException", e);
+                            externalCallback.onDetectionFailure(new HotwordDetectionServiceFailure(
+                                    CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
+                                    "Copy audio stream failure."));
+                        } catch (RemoteException e1) {
+                            notifyOnDetectorRemoteException();
+                            throw e1;
+                        }
                         return;
                     }
-                    externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
-                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
-                            + " bits from hotword trusted process");
+                    try {
+                        externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
+                        Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+                                + " bits from hotword trusted process");
+                    } catch (RemoteException e) {
+                        notifyOnDetectorRemoteException();
+                        HotwordMetricsLogger.writeDetectorEvent(
+                                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                                HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_DETECTED_EXCEPTION,
+                                mVoiceInteractionServiceUid);
+                        throw e;
+                    }
                     if (mDebugHotwordLogging) {
                         Slog.i(TAG, "Egressed detected result: " + newResult);
                     }
@@ -180,7 +207,16 @@
                         return;
                     }
                     mValidatingDspTrigger = false;
-                    externalCallback.onRejected(result);
+                    try {
+                        externalCallback.onRejected(result);
+                    } catch (RemoteException e) {
+                        notifyOnDetectorRemoteException();
+                        HotwordMetricsLogger.writeDetectorEvent(
+                                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                                HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION,
+                                mVoiceInteractionServiceUid);
+                        throw e;
+                    }
                     mLastHotwordRejectedResult = result;
                     if (mDebugHotwordLogging && result != null) {
                         Slog.i(TAG, "Egressed rejected result: " + result);
@@ -215,6 +251,7 @@
                                     HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
                                     HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
                                     mVoiceInteractionServiceUid);
+                            notifyOnDetectorRemoteException();
                         }
                     },
                     MAX_VALIDATION_TIMEOUT_MILLIS,
@@ -247,6 +284,7 @@
                         HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
                         HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION,
                         mVoiceInteractionServiceUid);
+                notifyOnDetectorRemoteException();
             }
             mValidatingDspTrigger = false;
         }
@@ -260,6 +298,7 @@
                     HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
                     HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION,
                     mVoiceInteractionServiceUid);
+            notifyOnDetectorRemoteException();
         }
 
         mPerformingExternalSourceHotwordDetection = false;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 1ba3975..025e1dc 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -30,7 +30,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Disabled;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
@@ -41,7 +41,6 @@
 import android.media.AudioManagerInternal;
 import android.media.permission.Identity;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
@@ -69,6 +68,7 @@
 import com.android.internal.infra.ServiceConnector;
 import com.android.server.LocalServices;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
 
 import java.io.PrintWriter;
 import java.time.Instant;
@@ -88,8 +88,7 @@
     static final boolean DEBUG = false;
 
     /**
-     * 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
+     * Implementors of the HotwordDetectionService must not augment the phrase IDs which are
      * supplied via HotwordDetectionService
      * #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback).
      *
@@ -103,7 +102,7 @@
      * </p>
      */
     @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Disabled
     public static final long ENFORCE_HOTWORD_PHRASE_ID = 215066299L;
 
     private static final String KEY_RESTART_PERIOD_IN_SECONDS = "restart_period_in_seconds";
@@ -147,6 +146,8 @@
     @GuardedBy("mLock")
     private boolean mDebugHotwordLogging = false;
 
+    private DetectorRemoteExceptionListener mRemoteExceptionListener;
+
     /**
      * For multiple detectors feature, we only support one AlwaysOnHotwordDetector and one
      * SoftwareHotwordDetector at the same time. We use SparseArray with detector type as the key
@@ -159,7 +160,8 @@
     HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName,
             ComponentName visualQueryDetectionServiceName, int userId,
-            boolean bindInstantServiceAllowed, int detectorType) {
+            boolean bindInstantServiceAllowed, int detectorType,
+            DetectorRemoteExceptionListener listener) {
         mLock = lock;
         mContext = context;
         mVoiceInteractionServiceUid = voiceInteractionServiceUid;
@@ -168,6 +170,7 @@
         mVisualQueryDetectionComponentName = visualQueryDetectionServiceName;
         mUser = userId;
         mDetectorType = detectorType;
+        mRemoteExceptionListener = listener;
         mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
                 KEY_RESTART_PERIOD_IN_SECONDS, 0);
 
@@ -251,6 +254,7 @@
     void cancelLocked() {
         Slog.v(TAG, "cancelLocked");
         clearDebugHotwordLoggingTimeoutLocked();
+        mRemoteExceptionListener = null;
         runForEachDetectorSessionLocked((session) -> {
             session.destroyLocked();
         });
@@ -772,7 +776,8 @@
             }
             session = new DspTrustedHotwordDetectorSession(mRemoteHotwordDetectionService,
                     mLock, mContext, token, callback, mVoiceInteractionServiceUid,
-                    mVoiceInteractorIdentity, mScheduledExecutorService, mDebugHotwordLogging);
+                    mVoiceInteractorIdentity, mScheduledExecutorService, mDebugHotwordLogging,
+                    mRemoteExceptionListener);
         } else if (detectorType == HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
             if (mRemoteVisualQueryDetectionService == null) {
                 mRemoteVisualQueryDetectionService =
@@ -781,7 +786,7 @@
             session = new VisualQueryDetectorSession(
                     mRemoteVisualQueryDetectionService, mLock, mContext, token, callback,
                     mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
-                    mScheduledExecutorService, mDebugHotwordLogging);
+                    mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener);
         } else {
             if (mRemoteHotwordDetectionService == null) {
                 mRemoteHotwordDetectionService =
@@ -790,7 +795,7 @@
             session = new SoftwareTrustedHotwordDetectorSession(
                     mRemoteHotwordDetectionService, mLock, mContext, token, callback,
                     mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
-                    mScheduledExecutorService, mDebugHotwordLogging);
+                    mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener);
         }
         mDetectorSessions.put(detectorType, session);
         session.initialize(options, sharedMemory);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
index 522d832..367fb81 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
@@ -18,6 +18,8 @@
 
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
 
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_DETECTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED;
@@ -43,6 +45,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -68,10 +71,11 @@
             @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
             @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity,
-            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging,
+            @NonNull DetectorRemoteExceptionListener listener) {
         super(remoteHotwordDetectionService, lock, context, token, callback,
                 voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
-                logging);
+                logging, listener);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -118,13 +122,23 @@
                     try {
                         enforcePermissionsForDataDelivery();
                     } catch (SecurityException e) {
+                        Slog.w(TAG, "Ignoring #onDetected due to a SecurityException", e);
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
                                 METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
                                 mVoiceInteractionServiceUid);
-                        mSoftwareCallback.onError(new HotwordDetectionServiceFailure(
-                                CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
-                                "Security exception occurs in #onDetected method."));
+                        try {
+                            mSoftwareCallback.onError(new HotwordDetectionServiceFailure(
+                                    CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
+                                    "Security exception occurs in #onDetected method."));
+                        } catch (RemoteException e1) {
+                            notifyOnDetectorRemoteException();
+                            HotwordMetricsLogger.writeDetectorEvent(
+                                    HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+                                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                                    mVoiceInteractionServiceUid);
+                            throw e1;
+                        }
                         return;
                     }
                     saveProximityValueToBundle(result);
@@ -132,13 +146,32 @@
                     try {
                         newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
                     } catch (IOException e) {
+                        Slog.w(TAG, "Ignoring #onDetected due to a IOException", e);
                         // TODO: Write event
-                        mSoftwareCallback.onError(new HotwordDetectionServiceFailure(
-                                CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
-                                "Copy audio stream failure."));
+                        try {
+                            mSoftwareCallback.onError(new HotwordDetectionServiceFailure(
+                                    CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
+                                    "Copy audio stream failure."));
+                        } catch (RemoteException e1) {
+                            notifyOnDetectorRemoteException();
+                            HotwordMetricsLogger.writeDetectorEvent(
+                                    HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+                                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                                    mVoiceInteractionServiceUid);
+                            throw e1;
+                        }
                         return;
                     }
-                    mSoftwareCallback.onDetected(newResult, null, null);
+                    try {
+                        mSoftwareCallback.onDetected(newResult, null, null);
+                    } catch (RemoteException e1) {
+                        notifyOnDetectorRemoteException();
+                        HotwordMetricsLogger.writeDetectorEvent(
+                                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+                                HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_DETECTED_EXCEPTION,
+                                mVoiceInteractionServiceUid);
+                        throw e1;
+                    }
                     Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
                             + " bits from hotword trusted process");
                     if (mDebugHotwordLogging) {
@@ -204,6 +237,7 @@
                     HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
                     HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION,
                     mVoiceInteractionServiceUid);
+            notifyOnDetectorRemoteException();
         }
 
         // Restart listening from microphone if the hotword process has been restarted.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index c397812..afe5dab 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -38,6 +38,7 @@
 
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
 
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -64,13 +65,15 @@
             @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
             @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity,
-            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging,
+            @NonNull DetectorRemoteExceptionListener listener) {
         super(remoteService, lock, context, token, callback,
                 voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
-                logging);
+                logging, listener);
         mEgressingData = false;
         mQueryStreaming = false;
         mAttentionListener = null;
+        // TODO: handle notify RemoteException to client
     }
 
     @Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 96b69f8..929e033 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -743,7 +743,15 @@
             mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
                     mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
                     mHotwordDetectionComponentName, mVisualQueryDetectionComponentName, mUser,
-                    /* bindInstantServiceAllowed= */ false, detectorType);
+                    /* bindInstantServiceAllowed= */ false, detectorType,
+                    (token1, detectorType1) -> {
+                        try {
+                            mService.detectorRemoteExceptionOccurred(token1, detectorType1);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Fail to notify client detector remote "
+                                    + "exception occurred.");
+                        }
+                    });
         } else if (detectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
             // TODO: Logger events should be handled in session instead. Temporary adding the
             //  checking to prevent confusion so VisualQueryDetection events won't be logged if the
@@ -1080,4 +1088,8 @@
         // client always get the callback even if session is unexpectedly closed.
         mServiceStub.setSessionWindowVisible(connection.mToken, false);
     }
+
+    interface DetectorRemoteExceptionListener {
+        void onDetectorRemoteException(@NonNull IBinder token, int detectorType);
+    }
 }
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 6b2bea0..97538c1 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -76,7 +76,10 @@
     }
 
     /**
-     * Request Telecom set the call state to active.
+     * Request Telecom set the call state to active. This method should be called when either an
+     * outgoing call is ready to go active or a held call is ready to go active again. For incoming
+     * calls that are ready to be answered, use
+     * {@link CallControl#answer(int, Executor, OutcomeReceiver)}.
      *
      * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
      *                 will be called on.
@@ -106,6 +109,43 @@
     }
 
     /**
+     * Request Telecom answer an incoming call.  For outgoing calls and calls that have been placed
+     * on hold, use {@link CallControl#setActive(Executor, OutcomeReceiver)}.
+     *
+     * @param videoState to report to Telecom. Telecom will store VideoState in the event another
+     *                   service/device requests it in order to continue the call on another screen.
+     * @param executor   The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                   will be called on.
+     * @param callback   that will be completed on the Telecom side that details success or failure
+     *                   of the requested operation.
+     *
+     *                   {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                   switched the call state to active
+     *
+     *                   {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+     *                   the call state to active.  A {@link CallException} will be passed
+     *                   that details why the operation failed.
+     */
+    public void answer(@android.telecom.CallAttributes.CallType int videoState,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        validateVideoState(videoState);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.answer(videoState, mCallId,
+                        new CallControlResultReceiver("answer", executor, callback));
+
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
      * Request Telecom set the call state to inactive. This the same as hold for two call endpoints
      * but can be extended to setting a meeting to inactive.
      *
@@ -343,4 +383,13 @@
         }
     }
 
+    /** @hide */
+    private void validateVideoState(@android.telecom.CallAttributes.CallType int videoState) {
+        if (videoState != CallAttributes.AUDIO_CALL && videoState != CallAttributes.VIDEO_CALL) {
+            throw new IllegalArgumentException(TextUtils.formatSimple(
+                    "The VideoState argument passed in, %d , is not a valid VideoState. The "
+                            + "VideoState choices are limited to CallAttributes.AUDIO_CALL or"
+                            + "CallAttributes.VIDEO_CALL", videoState));
+        }
+    }
 }
diff --git a/telecomm/java/android/telecom/CallControlCallback.java b/telecomm/java/android/telecom/CallControlCallback.java
index aadf337..35e2fd4 100644
--- a/telecomm/java/android/telecom/CallControlCallback.java
+++ b/telecomm/java/android/telecom/CallControlCallback.java
@@ -75,24 +75,17 @@
             @NonNull Consumer<Boolean> wasCompleted);
 
     /**
-     * Telecom is informing the client to reject the incoming call
-     *
-     * @param wasCompleted The {@link Consumer} to be completed. If the client can reject the
-     *                     incoming call, {@link Consumer#accept(Object)} should be called with
-     *                     {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
-     *                     should  be called with {@link Boolean#FALSE}.
-     */
-    void onReject(@NonNull Consumer<Boolean> wasCompleted);
-
-    /**
      * Telecom is informing the client to disconnect the call
      *
-     * @param wasCompleted The {@link Consumer} to be completed. If the client can disconnect the
-     *                     call on their end, {@link Consumer#accept(Object)} should be called with
-     *                     {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
-     *                     should  be called with {@link Boolean#FALSE}.
+     * @param disconnectCause represents the cause for disconnecting the call.
+     * @param wasCompleted    The {@link Consumer} to be completed. If the client can disconnect
+     *                        the call on their end, {@link Consumer#accept(Object)} should be
+     *                        called with {@link Boolean#TRUE}. Otherwise,
+     *                        {@link Consumer#accept(Object)} should  be called with
+     *                        {@link Boolean#FALSE}.
      */
-    void onDisconnect(@NonNull Consumer<Boolean> wasCompleted);
+    void onDisconnect(@NonNull DisconnectCause disconnectCause,
+            @NonNull Consumer<Boolean> wasCompleted);
 
     /**
      * Telecom is informing the client to set the call in streaming.
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
index e44e2b3..71e9184 100644
--- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -28,6 +28,7 @@
 import android.telecom.CallEndpoint;
 import android.telecom.CallEventCallback;
 import android.telecom.CallException;
+import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccountHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -142,7 +143,6 @@
         private static final String ON_SET_ACTIVE = "onSetActive";
         private static final String ON_SET_INACTIVE = "onSetInactive";
         private static final String ON_ANSWER = "onAnswer";
-        private static final String ON_REJECT = "onReject";
         private static final String ON_DISCONNECT = "onDisconnect";
         private static final String ON_STREAMING_STARTED = "onStreamingStarted";
         private static final String ON_REQ_ENDPOINT_CHANGE = "onRequestEndpointChange";
@@ -151,9 +151,9 @@
         private static final String ON_CALL_STREAMING_FAILED = "onCallStreamingFailed";
         private static final String ON_EVENT = "onEvent";
 
-        private void handleHandshakeCallback(String action, String callId, int code,
-                ResultReceiver ackResultReceiver) {
-            Log.i(TAG, TextUtils.formatSimple("hHC: id=[%s], action=[%s]", callId, action));
+        private void handleCallEventCallback(String action, String callId,
+                ResultReceiver ackResultReceiver, Object... args) {
+            Log.i(TAG, TextUtils.formatSimple("hCEC: id=[%s], action=[%s]", callId, action));
             // lookup the callEventCallback associated with the particular call
             TransactionalCall call = mCallIdToTransactionalCall.get(callId);
 
@@ -174,16 +174,13 @@
                             case ON_SET_INACTIVE:
                                 callback.onSetInactive(outcomeReceiverWrapper);
                                 break;
-                            case ON_REJECT:
-                                callback.onReject(outcomeReceiverWrapper);
-                                untrackCall(callId);
-                                break;
                             case ON_DISCONNECT:
-                                callback.onDisconnect(outcomeReceiverWrapper);
+                                callback.onDisconnect((DisconnectCause) args[0],
+                                        outcomeReceiverWrapper);
                                 untrackCall(callId);
                                 break;
                             case ON_ANSWER:
-                                callback.onAnswer(code, outcomeReceiverWrapper);
+                                callback.onAnswer((int) args[0], outcomeReceiverWrapper);
                                 break;
                             case ON_STREAMING_STARTED:
                                 callback.onCallStreamingStarted(outcomeReceiverWrapper);
@@ -231,28 +228,23 @@
 
         @Override
         public void onSetActive(String callId, ResultReceiver resultReceiver) {
-            handleHandshakeCallback(ON_SET_ACTIVE, callId, 0, resultReceiver);
+            handleCallEventCallback(ON_SET_ACTIVE, callId, resultReceiver);
         }
 
-
         @Override
         public void onSetInactive(String callId, ResultReceiver resultReceiver) {
-            handleHandshakeCallback(ON_SET_INACTIVE, callId, 0, resultReceiver);
+            handleCallEventCallback(ON_SET_INACTIVE, callId, resultReceiver);
         }
 
         @Override
         public void onAnswer(String callId, int videoState, ResultReceiver resultReceiver) {
-            handleHandshakeCallback(ON_ANSWER, callId, videoState, resultReceiver);
+            handleCallEventCallback(ON_ANSWER, callId, resultReceiver, videoState);
         }
 
         @Override
-        public void onReject(String callId, ResultReceiver resultReceiver) {
-            handleHandshakeCallback(ON_REJECT, callId, 0, resultReceiver);
-        }
-
-        @Override
-        public void onDisconnect(String callId, ResultReceiver resultReceiver) {
-            handleHandshakeCallback(ON_DISCONNECT, callId, 0, resultReceiver);
+        public void onDisconnect(String callId, DisconnectCause cause,
+                ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_DISCONNECT, callId, resultReceiver, cause);
         }
 
         @Override
@@ -308,7 +300,7 @@
 
         @Override
         public void onCallStreamingStarted(String callId, ResultReceiver resultReceiver) {
-            handleHandshakeCallback(ON_STREAMING_STARTED, callId, 0, resultReceiver);
+            handleCallEventCallback(ON_STREAMING_STARTED, callId, resultReceiver);
         }
 
         @Override
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index 3e651e9..5e2c923 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -27,6 +27,7 @@
  */
 oneway interface ICallControl {
     void setActive(String callId, in ResultReceiver callback);
+    void answer(int videoState, String callId, in ResultReceiver callback);
     void setInactive(String callId, in ResultReceiver callback);
     void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
     void startCallStreaming(String callId, in ResultReceiver callback);
diff --git a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
index dd61d17..213cafb 100644
--- a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
@@ -23,6 +23,7 @@
 import android.os.ResultReceiver;
 import android.telecom.CallAudioState;
 import android.telecom.CallException;
+import android.telecom.DisconnectCause;
 import java.util.List;
 
 /**
@@ -36,8 +37,7 @@
     void onSetActive(String callId, in ResultReceiver callback);
     void onSetInactive(String callId, in ResultReceiver callback);
     void onAnswer(String callId, int videoState, in ResultReceiver callback);
-    void onReject(String callId, in ResultReceiver callback);
-    void onDisconnect(String callId, in ResultReceiver callback);
+    void onDisconnect(String callId, in DisconnectCause cause, in ResultReceiver callback);
     // -- Streaming related. Client registered call streaming capabilities should override
     void onCallStreamingStarted(String callId, in ResultReceiver callback);
     void onCallStreamingFailed(String callId, int reason);
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index 7eccd1a..728dfa6 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -24,6 +24,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.service.carrier.CarrierIdentifier;
+import android.telephony.SubscriptionInfo;
 import android.telephony.UiccAccessRule;
 import android.text.TextUtils;
 
@@ -451,6 +452,8 @@
                 + mPolicyRules
                 + ", accessRules="
                 + Arrays.toString(mAccessRules)
+                + ", iccid="
+                + SubscriptionInfo.givePrintableIccid(mIccid)
                 + ")";
     }
 }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index b418a02..3e2c7c4 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4401,7 +4401,6 @@
      *
      * @throws IllegalArgumentException if subscription is invalid.
      * @throws SecurityException if the caller doesn't have permissions required.
-     * @throws IllegalStateException if subscription service is not available.
      *
      * @hide
      */
@@ -4418,8 +4417,7 @@
             if (iSub != null) {
                 return iSub.getSubscriptionUserHandle(subscriptionId);
             } else {
-                throw new IllegalStateException("[getSubscriptionUserHandle]: "
-                        + "subscription service unavailable");
+                Log.e(LOG_TAG, "[getSubscriptionUserHandle]: subscription service unavailable");
             }
         } catch (RemoteException ex) {
             ex.rethrowAsRuntimeException();
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 174675f..a8fb36b 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -342,8 +342,8 @@
     /**
      * @return The SIM slot index associated with this ImsFeature.
      *
-     * @see SubscriptionManager#getSubscriptionIds(int) for more information on getting the
-     * subscription IDs associated with this slot.
+     * @see SubscriptionManager#getSubscriptionId(int) for more information on getting the
+     * subscription ID associated with this slot.
      * @hide
      */
     @SystemApi
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 3009bec..f8cf81c 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1255,6 +1255,15 @@
      *                     SOS_SMS or LOCATION_SHARING.
      * @param datagram encoded gateway datagram which is encrypted by the caller.
      *                 Datagram will be passed down to modem without any encoding or encryption.
+     * @param needFullScreenPointingUI If set to true, this indicates pointingUI app to open in full
+     *                                 screen mode if satellite communication needs pointingUI.
+     *                                 If this is set to false, pointingUI may be presented to the
+     *                                 user in collapsed view. Application may decide to mark this
+     *                                 flag as true when the user is sending data for the first time
+     *                                 or whenever there is a considerable idle time between
+     *                                 satellite activity. This decision should be done based upon
+     *                                 user activity and the application's ability to determine the
+     *                                 best possible UX experience for the user.
      * @param executor The executor on which the result listener will be called.
      * @param callback The callback object to which the result will be returned.
      *                 If datagram is sent successfully, then
@@ -1267,7 +1276,8 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void sendSatelliteDatagram(long datagramId, @DatagramType int datagramType,
-            @NonNull SatelliteDatagram datagram, @NonNull @CallbackExecutor Executor executor,
+            @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Long, SatelliteException> callback) {
         Objects.requireNonNull(datagram);
         Objects.requireNonNull(executor);
@@ -1299,7 +1309,7 @@
                     }
                 };
                 telephony.sendSatelliteDatagram(mSubId, datagramId, datagramType, datagram,
-                        receiver);
+                        needFullScreenPointingUI, receiver);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteService.java b/telephony/java/android/telephony/satellite/stub/SatelliteService.java
index 3861c5b..5b96e34 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteService.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteService.java
@@ -20,7 +20,8 @@
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
-import android.util.Log;
+
+import com.android.telephony.Rlog;
 
 /**
  * Main SatelliteService implementation, which binds via the Telephony SatelliteServiceController.
@@ -44,7 +45,7 @@
  * @hide
  */
 public class SatelliteService extends Service {
-    public static final String TAG = "SatelliteService";
+    private static final String TAG = "SatelliteService";
 
     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     public static final String SERVICE_INTERFACE = "android.telephony.satellite.SatelliteService";
@@ -55,7 +56,7 @@
     @Override
     public IBinder onBind(Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            Log.e(TAG, "SatelliteService bound");
+            Rlog.d(TAG, "SatelliteService bound");
             return new SatelliteImplBase(Runnable::run).getBinder();
         }
         return null;
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index f4801c2..ef9dc3e 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2938,13 +2938,16 @@
     * @param datagramId An id that uniquely identifies datagram requested to be sent.
     * @param datagramType Type of datagram.
     * @param datagram Datagram to send over satellite.
+    * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
+    *                                 full screen mode.
     * @param receiver Result receiver to get the datagramId if datagram is sent successfully else
     *                 error code of the request.
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
     void sendSatelliteDatagram(int subId, long datagramId, int datagramType,
-            in SatelliteDatagram datagram, in ResultReceiver receiver);
+             in SatelliteDatagram datagram, in boolean needFullScreenPointingUI,
+             in ResultReceiver receiver);
 
     /**
      * Request to get whether satellite communication is allowed for the current location.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 08786d3..fb6fb22 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -19,12 +19,15 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
+import android.platform.test.rule.SettingOverrideRule
+import android.provider.Settings
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerBuilder
 import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
+import org.junit.ClassRule
 import org.junit.FixMethodOrder
 import org.junit.Ignore
 import org.junit.Test
@@ -130,5 +133,16 @@
         fun getParams(): Collection<FlickerTest> {
             return FlickerTestFactory.nonRotationTests()
         }
+
+        /**
+         * Ensures that posted notifications will be visible on the lockscreen and not
+         * suppressed due to being marked as seen.
+         */
+        @ClassRule
+        @JvmField
+        val disableUnseenNotifFilterRule = SettingOverrideRule(
+            Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+            /* value= */ "0",
+        )
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
index a5d85cc..32276d6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -18,6 +18,8 @@
 
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
+import android.platform.test.rule.SettingOverrideRule
+import android.provider.Settings
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerBuilder
 import com.android.server.wm.flicker.FlickerTest
@@ -25,6 +27,7 @@
 import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
 import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
+import org.junit.ClassRule
 import org.junit.FixMethodOrder
 import org.junit.Ignore
 import org.junit.Test
@@ -145,5 +148,16 @@
         fun getParams(): Collection<FlickerTest> {
             return FlickerTestFactory.nonRotationTests()
         }
+
+        /**
+         * Ensures that posted notifications will be visible on the lockscreen and not
+         * suppressed due to being marked as seen.
+         */
+        @ClassRule
+        @JvmField
+        val disableUnseenNotifFilterRule = SettingOverrideRule(
+            Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+            /* value= */ "0",
+        )
     }
 }