Merge "Launch coroutines in the repository only if the refactor flag is enabled" into udc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index f779b4d..b0b3b1f 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -319,6 +319,8 @@
     private SensorManager mSensorManager;
     private final boolean mUseMotionSensor;
     private Sensor mMotionSensor;
+    private final boolean mIsLocationPrefetchEnabled;
+    @Nullable
     private LocationRequest mLocationRequest;
     private Intent mIdleIntent;
     private Bundle mIdleIntentOptions;
@@ -2460,6 +2462,11 @@
             return null;
         }
 
+        boolean isLocationPrefetchEnabled() {
+            return mContext.getResources().getBoolean(
+                   com.android.internal.R.bool.config_autoPowerModePrefetchLocation);
+        }
+
         boolean useMotionSensor() {
             return mContext.getResources().getBoolean(
                    com.android.internal.R.bool.config_autoPowerModeUseMotionSensor);
@@ -2489,6 +2496,7 @@
         mAppStateTracker = mInjector.getAppStateTracker(context,
                 AppSchedulingModuleThread.get().getLooper());
         LocalServices.addService(AppStateTracker.class, mAppStateTracker);
+        mIsLocationPrefetchEnabled = mInjector.isLocationPrefetchEnabled();
         mUseMotionSensor = mInjector.useMotionSensor();
     }
 
@@ -2602,8 +2610,7 @@
                     mMotionSensor = mInjector.getMotionSensor();
                 }
 
-                if (getContext().getResources().getBoolean(
-                        com.android.internal.R.bool.config_autoPowerModePrefetchLocation)) {
+                if (mIsLocationPrefetchEnabled) {
                     mLocationRequest = new LocationRequest.Builder(/*intervalMillis=*/ 0)
                         .setQuality(LocationRequest.QUALITY_HIGH_ACCURACY)
                         .setMaxUpdates(1)
@@ -3779,34 +3786,40 @@
             case STATE_SENSING:
                 cancelSensingTimeoutAlarmLocked();
                 moveToStateLocked(STATE_LOCATING, reason);
-                scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT);
-                LocationManager locationManager = mInjector.getLocationManager();
-                if (locationManager != null
-                        && locationManager.getProvider(LocationManager.FUSED_PROVIDER) != null) {
-                    locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER,
-                            mLocationRequest,
-                            AppSchedulingModuleThread.getExecutor(),
-                            mGenericLocationListener);
-                    mLocating = true;
+                if (mIsLocationPrefetchEnabled) {
+                    scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT);
+                    LocationManager locationManager = mInjector.getLocationManager();
+                    if (locationManager != null
+                            && locationManager.getProvider(LocationManager.FUSED_PROVIDER)
+                                    != null) {
+                        locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER,
+                                mLocationRequest,
+                                AppSchedulingModuleThread.getExecutor(),
+                                mGenericLocationListener);
+                        mLocating = true;
+                    } else {
+                        mHasFusedLocation = false;
+                    }
+                    if (locationManager != null
+                            && locationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
+                        mHasGps = true;
+                        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
+                                1000, 5, mGpsLocationListener, mHandler.getLooper());
+                        mLocating = true;
+                    } else {
+                        mHasGps = false;
+                    }
+                    // If we have a location provider, we're all set, the listeners will move state
+                    // forward.
+                    if (mLocating) {
+                        break;
+                    }
+                    // Otherwise, we have to move from locating into idle maintenance.
                 } else {
-                    mHasFusedLocation = false;
-                }
-                if (locationManager != null
-                        && locationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
-                    mHasGps = true;
-                    locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5,
-                            mGpsLocationListener, mHandler.getLooper());
-                    mLocating = true;
-                } else {
-                    mHasGps = false;
-                }
-                // If we have a location provider, we're all set, the listeners will move state
-                // forward.
-                if (mLocating) {
-                    break;
+                    mLocating = false;
                 }
 
-                // Otherwise, we have to move from locating into idle maintenance.
+                // We're not doing any locating work, so move on to the next state.
             case STATE_LOCATING:
                 cancelAlarmLocked();
                 cancelLocatingLocked();
@@ -5303,15 +5316,19 @@
                 pw.print("  "); pw.print(mStationaryListeners.size());
                 pw.println(" stationary listeners registered");
             }
-            pw.print("  mLocating="); pw.print(mLocating);
-            pw.print(" mHasGps="); pw.print(mHasGps);
-            pw.print(" mHasFused="); pw.print(mHasFusedLocation);
-            pw.print(" mLocated="); pw.println(mLocated);
-            if (mLastGenericLocation != null) {
-                pw.print("  mLastGenericLocation="); pw.println(mLastGenericLocation);
-            }
-            if (mLastGpsLocation != null) {
-                pw.print("  mLastGpsLocation="); pw.println(mLastGpsLocation);
+            if (mIsLocationPrefetchEnabled) {
+                pw.print("  mLocating="); pw.print(mLocating);
+                pw.print(" mHasGps="); pw.print(mHasGps);
+                pw.print(" mHasFused="); pw.print(mHasFusedLocation);
+                pw.print(" mLocated="); pw.println(mLocated);
+                if (mLastGenericLocation != null) {
+                    pw.print("  mLastGenericLocation="); pw.println(mLastGenericLocation);
+                }
+                if (mLastGpsLocation != null) {
+                    pw.print("  mLastGpsLocation="); pw.println(mLastGpsLocation);
+                }
+            } else {
+                pw.println("  Location prefetching disabled");
             }
             pw.print("  mState="); pw.print(stateToString(mState));
             pw.print(" mLightState=");
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 332c53c..d97f718 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3632,18 +3632,12 @@
   }
 
   public final class AutofillManager {
-    method public void clearAutofillRequestCallback();
-    method @RequiresPermission(android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS) public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
     field public static final String ANY_HINT = "any";
     field public static final int FLAG_SMART_SUGGESTION_OFF = 0; // 0x0
     field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1
     field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0
   }
 
-  public interface AutofillRequestCallback {
-    method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
-  }
-
 }
 
 package android.view.contentcapture {
@@ -3767,11 +3761,6 @@
     method @NonNull public static android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(@NonNull android.widget.inline.InlinePresentationSpec, @NonNull String, @Nullable String[], @NonNull String, boolean);
   }
 
-  public static final class InlineSuggestionsRequest.Builder {
-    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean);
-    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean);
-  }
-
   public final class InlineSuggestionsResponse implements android.os.Parcelable {
     method @NonNull public static android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(@NonNull java.util.List<android.view.inputmethod.InlineSuggestion>);
   }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 61d6787..2ae7216 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -403,8 +403,11 @@
     private static final String KEY_SPLASH_SCREEN_STYLE =
             "android.activity.splashScreenStyle";
 
-    /** See {@link #setTransientLaunch()}. */
-    private static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch";
+    /**
+     * See {@link #setTransientLaunch()}.
+     * @hide
+     */
+    public static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch";
 
     /** see {@link #makeLaunchIntoPip(PictureInPictureParams)}. */
     private static final String KEY_LAUNCH_INTO_PIP_PARAMS =
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 36e5762..3c6ff28 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -137,7 +137,7 @@
      * activities inside it belong to a managed profile user, and that user has just
      * been locked.
      */
-    void onTaskProfileLocked(in ActivityManager.RunningTaskInfo taskInfo);
+    void onTaskProfileLocked(in ActivityManager.RunningTaskInfo taskInfo, int userId);
 
     /**
      * Called when a task snapshot got updated.
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 99a7fa2..705b5ee 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -407,7 +407,7 @@
     }
 
     private static void checkPendingIntent(int flags, @NonNull Intent intent,
-            @NonNull Context context) {
+            @NonNull Context context, boolean isActivityResultType) {
         final boolean isFlagImmutableSet = (flags & PendingIntent.FLAG_IMMUTABLE) != 0;
         final boolean isFlagMutableSet = (flags & PendingIntent.FLAG_MUTABLE) != 0;
         final String packageName = context.getPackageName();
@@ -428,11 +428,12 @@
                 throw new IllegalArgumentException(msg);
         }
 
-        // For apps with target SDK < U, warn that creation or retrieval of a mutable
-        // implicit PendingIntent will be blocked from target SDK U onwards for security
-        // reasons. The block itself happens on the server side, but this warning has to
-        // stay here to preserve the client side stack trace for app developers.
-        if (isNewMutableDisallowedImplicitPendingIntent(flags, intent)
+        // For apps with target SDK < U, warn that creation or retrieval of a mutable implicit
+        // PendingIntent that is not of type {@link ActivityManager#INTENT_SENDER_ACTIVITY_RESULT}
+        // will be blocked from target SDK U onwards for security reasons. The block itself
+        // happens on the server side, but this warning has to stay here to preserve the client
+        // side stack trace for app developers.
+        if (isNewMutableDisallowedImplicitPendingIntent(flags, intent, isActivityResultType)
                 && !Compatibility.isChangeEnabled(BLOCK_MUTABLE_IMPLICIT_PENDING_INTENT)) {
             String msg = "New mutable implicit PendingIntent: pkg=" + packageName
                     + ", action=" + intent.getAction()
@@ -445,7 +446,13 @@
 
     /** @hide */
     public static boolean isNewMutableDisallowedImplicitPendingIntent(int flags,
-            @NonNull Intent intent) {
+            @NonNull Intent intent, boolean isActivityResultType) {
+        if (isActivityResultType) {
+            // Pending intents of type {@link ActivityManager#INTENT_SENDER_ACTIVITY_RESULT}
+            // should be ignored as they are intrinsically tied to a target which means they
+            // are already explicit.
+            return false;
+        }
         boolean isFlagNoCreateSet = (flags & PendingIntent.FLAG_NO_CREATE) != 0;
         boolean isFlagMutableSet = (flags & PendingIntent.FLAG_MUTABLE) != 0;
         boolean isImplicit = (intent.getComponent() == null) && (intent.getPackage() == null);
@@ -534,7 +541,7 @@
             @NonNull Intent intent, int flags, Bundle options, UserHandle user) {
         String packageName = context.getPackageName();
         String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());
-        checkPendingIntent(flags, intent, context);
+        checkPendingIntent(flags, intent, context, /* isActivityResultType */ false);
         try {
             intent.migrateExtraStreamToClipData(context);
             intent.prepareToLeaveProcess(context);
@@ -668,7 +675,7 @@
             intents[i].migrateExtraStreamToClipData(context);
             intents[i].prepareToLeaveProcess(context);
             resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver());
-            checkPendingIntent(flags, intents[i], context);
+            checkPendingIntent(flags, intents[i], context, /* isActivityResultType */ false);
         }
         try {
             IIntentSender target =
@@ -721,7 +728,7 @@
             Intent intent, int flags, UserHandle userHandle) {
         String packageName = context.getPackageName();
         String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());
-        checkPendingIntent(flags, intent, context);
+        checkPendingIntent(flags, intent, context, /* isActivityResultType */ false);
         try {
             intent.prepareToLeaveProcess(context);
             IIntentSender target =
@@ -800,7 +807,7 @@
             Intent intent, int flags, int serviceKind) {
         String packageName = context.getPackageName();
         String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());
-        checkPendingIntent(flags, intent, context);
+        checkPendingIntent(flags, intent, context, /* isActivityResultType */ false);
         try {
             intent.prepareToLeaveProcess(context);
             IIntentSender target =
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 774bc06..0290cee 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -154,8 +154,18 @@
     }
 
     @Override
+    public void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId)
+            throws RemoteException {
+        onTaskProfileLocked(taskInfo);
+    }
+
+    /**
+     * @deprecated see {@link #onTaskProfileLocked(RunningTaskInfo, int)}
+     */
+    @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void onTaskProfileLocked(RunningTaskInfo taskInfo) throws RemoteException {
+    public void onTaskProfileLocked(RunningTaskInfo taskInfo)
+            throws RemoteException {
     }
 
     @Override
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 09d2db8..9ebb058 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -30,6 +30,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Contains information about the request that initiated this UX flow.
@@ -64,6 +66,9 @@
     @Nullable
     private final CreateCredentialRequest mCreateCredentialRequest;
 
+    @NonNull
+    private final List<String> mDefaultProviderIds;
+
     @Nullable
     private final GetCredentialRequest mGetCredentialRequest;
 
@@ -83,7 +88,8 @@
             @NonNull String appPackageName) {
         return new RequestInfo(
                 token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
-                /*hasPermissionToOverrideDefault=*/ false);
+                /*hasPermissionToOverrideDefault=*/ false,
+                /*defaultProviderIds=*/ new ArrayList<>());
     }
 
     /**
@@ -94,10 +100,11 @@
     @NonNull
     public static RequestInfo newCreateRequestInfo(
             @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
-            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault) {
+            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
+            @NonNull List<String> defaultProviderIds) {
         return new RequestInfo(
                 token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
-                hasPermissionToOverrideDefault);
+                hasPermissionToOverrideDefault, defaultProviderIds);
     }
 
     /** Creates new {@code RequestInfo} for a get-credential flow. */
@@ -107,7 +114,8 @@
             @NonNull String appPackageName) {
         return new RequestInfo(
                 token, TYPE_GET, appPackageName, null, getCredentialRequest,
-                /*hasPermissionToOverrideDefault=*/ false);
+                /*hasPermissionToOverrideDefault=*/ false,
+                /*defaultProviderIds=*/ new ArrayList<>());
     }
 
 
@@ -149,6 +157,20 @@
     }
 
     /**
+     * Returns default provider identifier (flattened component name) configured from the user
+     * settings.
+     *
+     * Will only be possibly non-empty for the create use case. Not meaningful for the sign-in use
+     * case.
+     *
+     * @hide
+     */
+    @NonNull
+    public List<String> getDefaultProviderIds() {
+        return mDefaultProviderIds;
+    }
+
+    /**
      * Returns the non-null GetCredentialRequest when the type of the request is {@link
      * #TYPE_GET}, or null otherwise.
      */
@@ -161,13 +183,15 @@
             @NonNull String appPackageName,
             @Nullable CreateCredentialRequest createCredentialRequest,
             @Nullable GetCredentialRequest getCredentialRequest,
-            boolean hasPermissionToOverrideDefault) {
+            boolean hasPermissionToOverrideDefault,
+            @NonNull List<String> defaultProviderIds) {
         mToken = token;
         mType = type;
         mAppPackageName = appPackageName;
         mCreateCredentialRequest = createCredentialRequest;
         mGetCredentialRequest = getCredentialRequest;
         mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault;
+        mDefaultProviderIds = defaultProviderIds == null ? new ArrayList<>() : defaultProviderIds;
     }
 
     private RequestInfo(@NonNull Parcel in) {
@@ -188,6 +212,7 @@
         mCreateCredentialRequest = createCredentialRequest;
         mGetCredentialRequest = getCredentialRequest;
         mHasPermissionToOverrideDefault = in.readBoolean();
+        mDefaultProviderIds = in.createStringArrayList();
     }
 
     @Override
@@ -198,6 +223,7 @@
         dest.writeTypedObject(mCreateCredentialRequest, flags);
         dest.writeTypedObject(mGetCredentialRequest, flags);
         dest.writeBoolean(mHasPermissionToOverrideDefault);
+        dest.writeStringList(mDefaultProviderIds);
     }
 
     @Override
diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java
index bb36536..fac747c 100644
--- a/core/java/android/net/metrics/WakeupStats.java
+++ b/core/java/android/net/metrics/WakeupStats.java
@@ -80,18 +80,20 @@
                 break;
         }
 
-        switch (ev.dstHwAddr.getAddressType()) {
-            case MacAddress.TYPE_UNICAST:
-                l2UnicastCount++;
-                break;
-            case MacAddress.TYPE_MULTICAST:
-                l2MulticastCount++;
-                break;
-            case MacAddress.TYPE_BROADCAST:
-                l2BroadcastCount++;
-                break;
-            default:
-                break;
+        if (ev.dstHwAddr != null) {
+            switch (ev.dstHwAddr.getAddressType()) {
+                case MacAddress.TYPE_UNICAST:
+                    l2UnicastCount++;
+                    break;
+                case MacAddress.TYPE_MULTICAST:
+                    l2MulticastCount++;
+                    break;
+                case MacAddress.TYPE_BROADCAST:
+                    l2BroadcastCount++;
+                    break;
+                default:
+                    break;
+            }
         }
 
         increment(ethertypes, ev.ethertype);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 79e7574..4a46beb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4677,16 +4677,22 @@
                 "display_color_mode_vendor_hint";
 
         /**
-         * Whether or not the peak refresh rate should be forced. 0=no, 1=yes
+         * The user selected min refresh rate in frames per second.
+         *
+         * If this isn't set, 0 will be used.
          * @hide
          */
-        public static final String FORCE_PEAK_REFRESH_RATE = "force_peak_refresh_rate";
+        @Readable
+        public static final String MIN_REFRESH_RATE = "min_refresh_rate";
 
         /**
-         * Whether or not the peak refresh rate should be used for some content. 0=no, 1=yes
+         * The user selected peak refresh rate in frames per second.
+         *
+         * If this isn't set, the system falls back to a device specific default.
          * @hide
          */
-        public static final String SMOOTH_DISPLAY = "smooth_display";
+        @Readable
+        public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
 
         /**
          * The amount of time in milliseconds before the device goes to sleep or begins
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 4a848dd..8afae74 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -97,8 +97,6 @@
      */
     public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10;
 
-    // The flag value 0x20 has been defined in AutofillManager.
-
     /**
      * Indicates the request supports fill dialog presentation for the fields, the
      * system will send the request when the activity just started.
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 1a1df6f..751c675 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -75,12 +75,13 @@
     /**
      * Constructs an information instance of the credential provider.
      *
-     * @param context the context object
+     * @param context          the context object
      * @param serviceComponent the serviceComponent of the provider service
-     * @param userId the android userId for which the current process is running
+     * @param userId           the android userId for which the current process is running
      * @param isSystemProvider whether this provider is a system provider
      * @throws PackageManager.NameNotFoundException If provider service is not found
-     * @throws SecurityException If provider does not require the relevant permission
+     * @throws SecurityException                    If provider does not require the relevant
+     *                                              permission
      */
     public static CredentialProviderInfo create(
             @NonNull Context context,
@@ -99,13 +100,15 @@
     /**
      * Constructs an information instance of the credential provider.
      *
-     * @param context the context object
-     * @param serviceInfo the service info for the provider app. This must be retrieved from the
-     *     {@code PackageManager}
-     * @param isSystemProvider whether the provider app is a system provider
+     * @param context                              the context object
+     * @param serviceInfo                          the service info for the provider app. This must
+     *                                             be retrieved from the
+     *                                             {@code PackageManager}
+     * @param isSystemProvider                     whether the provider app is a system provider
      * @param disableSystemAppVerificationForTests whether to disable system app permission
-     *     verification so that tests can install system providers
-     * @param isEnabled whether the user enabled this provider
+     *                                             verification so that tests can install system
+     *                                             providers
+     * @param isEnabled                            whether the user enabled this provider
      * @throws SecurityException If provider does not require the relevant permission
      */
     public static CredentialProviderInfo create(
@@ -374,7 +377,6 @@
                 if (appInfo == null || serviceInfo == null) {
                     continue;
                 }
-
                 services.add(serviceInfo);
             } catch (SecurityException | PackageManager.NameNotFoundException e) {
                 Slog.e(TAG, "Error getting info for " + serviceInfo, e);
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 53a5fd5..cf2e6a6 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -18,7 +18,6 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
-import android.Manifest;
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
@@ -35,7 +34,7 @@
 import android.os.Looper;
 import android.os.OutcomeReceiver;
 import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.Objects;
 
@@ -226,7 +225,7 @@
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
             return mInterface.asBinder();
         }
-        Log.d(TAG, "Failed to bind with intent: " + intent);
+        Slog.w(TAG, "Failed to bind with intent: " + intent);
         return null;
     }
 
@@ -252,11 +251,6 @@
                             GetCredentialException>() {
                         @Override
                         public void onResult(BeginGetCredentialResponse result) {
-                            // If provider service does not possess the HYBRID permission, this
-                            // check will throw an exception in the provider process.
-                            if (result.getRemoteCredentialEntry() != null) {
-                                enforceRemoteEntryPermission();
-                            }
                             try {
                                 callback.onSuccess(result);
                             } catch (RemoteException e) {
@@ -274,15 +268,6 @@
                     }
             ));
         }
-        private void enforceRemoteEntryPermission() {
-            String permission =
-                    Manifest.permission.PROVIDE_REMOTE_CREDENTIALS;
-            getApplicationContext().enforceCallingOrSelfPermission(
-                    permission,
-                    String.format("Provider must have %s, in order to set a "
-                            + "remote entry", permission)
-            );
-        }
 
         @Override
         public void onBeginCreateCredential(BeginCreateCredentialRequest request,
@@ -305,11 +290,6 @@
                             BeginCreateCredentialResponse, CreateCredentialException>() {
                         @Override
                         public void onResult(BeginCreateCredentialResponse result) {
-                            // If provider service does not possess the HYBRID permission, this
-                            // check will throw an exception in the provider process.
-                            if (result.getRemoteCreateEntry() != null) {
-                                enforceRemoteEntryPermission();
-                            }
                             try {
                                 callback.onSuccess(result);
                             } catch (RemoteException e) {
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 6061a0f..7822dde 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -1063,21 +1063,6 @@
                 mActiveVisualQueryDetector = null;
             }
             mActiveDetectors.remove(detector);
-            shutdownHotwordDetectionServiceIfRequiredLocked();
-        }
-    }
-
-    private void shutdownHotwordDetectionServiceIfRequiredLocked() {
-        for (HotwordDetector detector : mActiveDetectors) {
-            if (detector.isUsingSandboxedDetectionService()) {
-                return;
-            }
-        }
-
-        try {
-            mSystemService.shutdownHotwordDetectionService();
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index f7b7d33..e39b3a1 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,7 +16,6 @@
 
 package android.view.autofill;
 
-import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
 import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
@@ -30,7 +29,6 @@
 import static android.view.autofill.Helper.toList;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -52,21 +50,16 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.metrics.LogMaker;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.AutofillService;
-import android.service.autofill.FillCallback;
 import android.service.autofill.FillEventHistory;
-import android.service.autofill.IFillCallback;
 import android.service.autofill.UserData;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -87,7 +80,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.accessibility.AccessibilityWindowInfo;
-import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.CheckBox;
 import android.widget.DatePicker;
@@ -187,12 +179,6 @@
  * shows an autofill save UI if the value of savable views have changed. If the user selects the
  * option to Save, the current value of the views is then sent to the autofill service.
  *
- * <p>There is another choice for the application to provide it's datasets to the Autofill framework
- * by setting an {@link AutofillRequestCallback} through
- * {@link #setAutofillRequestCallback(Executor, AutofillRequestCallback)}. The application can use
- * its callback instead of the default {@link AutofillService}. See
- * {@link AutofillRequestCallback} for more details.
- *
  * <h3 id="additional-notes">Additional notes</h3>
  *
  * <p>It is safe to call <code>AutofillManager</code> methods from any thread.
@@ -326,7 +312,6 @@
     /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
     /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
     /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8;
-    /** @hide */ public static final int FLAG_ENABLED_CLIENT_SUGGESTIONS = 0x20;
 
     // NOTE: flag below is used by the session start receiver only, hence it can have values above
     /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1;
@@ -653,11 +638,6 @@
     @GuardedBy("mLock")
     private boolean mEnabledForAugmentedAutofillOnly;
 
-    @GuardedBy("mLock")
-    @Nullable private AutofillRequestCallback mAutofillRequestCallback;
-    @GuardedBy("mLock")
-    @Nullable private Executor mRequestCallbackExecutor;
-
     /**
      * Indicates whether there is already a field to do a fill request after
      * the activity started.
@@ -2338,44 +2318,6 @@
         return new AutofillId(parent.getAutofillViewId(), virtualId);
     }
 
-    /**
-     * Sets the client's suggestions callback for autofill.
-     *
-     * @see AutofillRequestCallback
-     *
-     * @param executor specifies the thread upon which the callbacks will be invoked.
-     * @param callback which handles autofill request to provide client's suggestions.
-     *
-     * @hide
-     */
-    @TestApi
-    @RequiresPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-    public void setAutofillRequestCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull AutofillRequestCallback callback) {
-        if (mContext.checkSelfPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires PROVIDE_OWN_AUTOFILL_SUGGESTIONS permission!");
-        }
-
-        synchronized (mLock) {
-            mRequestCallbackExecutor = executor;
-            mAutofillRequestCallback = callback;
-        }
-    }
-
-    /**
-     * clears the client's suggestions callback for autofill.
-     *
-     * @hide
-     */
-    @TestApi
-    public void clearAutofillRequestCallback() {
-        synchronized (mLock) {
-            mRequestCallbackExecutor = null;
-            mAutofillRequestCallback = null;
-        }
-    }
-
     @GuardedBy("mLock")
     private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
             @NonNull AutofillValue value, int flags) {
@@ -2436,13 +2378,6 @@
                 }
             }
 
-            if (mAutofillRequestCallback != null) {
-                if (sDebug) {
-                    Log.d(TAG, "startSession with the client suggestions provider");
-                }
-                flags |= FLAG_ENABLED_CLIENT_SUGGESTIONS;
-            }
-
             mService.startSession(client.autofillClientGetActivityToken(),
                     mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
                     mCallback != null, flags, clientActivity,
@@ -2796,28 +2731,6 @@
         }
     }
 
-    private void onFillRequest(InlineSuggestionsRequest request,
-            CancellationSignal cancellationSignal, FillCallback callback) {
-        final AutofillRequestCallback autofillRequestCallback;
-        final Executor executor;
-        synchronized (mLock) {
-            autofillRequestCallback = mAutofillRequestCallback;
-            executor = mRequestCallbackExecutor;
-        }
-        if (autofillRequestCallback != null && executor != null) {
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                executor.execute(() ->
-                        autofillRequestCallback.onFillRequest(
-                                request, cancellationSignal, callback));
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        } else {
-            callback.onSuccess(null);
-        }
-    }
-
     /** @hide */
     public static final int SET_STATE_FLAG_ENABLED = 0x01;
     /** @hide */
@@ -4374,23 +4287,6 @@
         }
 
         @Override
-        public void requestFillFromClient(int id, InlineSuggestionsRequest request,
-                IFillCallback callback) {
-            final AutofillManager afm = mAfm.get();
-            if (afm != null) {
-                ICancellationSignal transport = CancellationSignal.createTransport();
-                try {
-                    callback.onCancellable(transport);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Error requesting a cancellation", e);
-                }
-
-                afm.onFillRequest(request, CancellationSignal.fromTransport(transport),
-                        new FillCallback(callback, id));
-            }
-        }
-
-        @Override
         public void notifyFillDialogTriggerIds(List<AutofillId> ids) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
diff --git a/core/java/android/view/autofill/AutofillRequestCallback.java b/core/java/android/view/autofill/AutofillRequestCallback.java
deleted file mode 100644
index 10a088b..0000000
--- a/core/java/android/view/autofill/AutofillRequestCallback.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2020 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.autofill;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.TestApi;
-import android.os.CancellationSignal;
-import android.service.autofill.FillCallback;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-/**
- * <p>This class is used to provide some input suggestions to the Autofill framework.
- *
- * <P>When the user is requested to input something, Autofill will try to query input suggestions
- * for the user choosing. If the application want to provide some internal input suggestions,
- * implements this callback and register via
- * {@link AutofillManager#setAutofillRequestCallback(java.util.concurrent.Executor,
- * AutofillRequestCallback)}. Autofill will callback the
- * {@link #onFillRequest(InlineSuggestionsRequest, CancellationSignal, FillCallback)} to request
- * input suggestions.
- *
- * <P>To make sure the callback to take effect, must register before the autofill session starts.
- * If the autofill session is started, calls {@link AutofillManager#cancel()} to finish current
- * session, and then the callback will be used at the next restarted session.
- *
- * <P>To create a {@link android.service.autofill.FillResponse}, application should fetch
- * {@link AutofillId}s from its view structure. Below is an example:
- * <pre class="prettyprint">
- * AutofillId usernameId = findViewById(R.id.username).getAutofillId();
- * AutofillId passwordId = findViewById(R.id.password).getAutofillId();
- * </pre>
- * To learn more about creating a {@link android.service.autofill.FillResponse}, read
- * <a href="/guide/topics/text/autofill-services#fill">Fill out client views</a>.
- *
- * <P>To fallback to the default {@link android.service.autofill.AutofillService}, just respond
- * a null of the {@link android.service.autofill.FillResponse}. And then Autofill will do a fill
- * request with the default {@link android.service.autofill.AutofillService}. Or clear the callback
- * from {@link AutofillManager} via {@link AutofillManager#clearAutofillRequestCallback()}. If the
- * client would like to keep no suggestions for the field, respond with an empty
- * {@link android.service.autofill.FillResponse} which has no dataset.
- *
- * <P>IMPORTANT: This should not be used for displaying anything other than input suggestions, or
- * the keyboard may choose to block your app from the inline strip.
- *
- * @hide
- */
-@TestApi
-public interface AutofillRequestCallback {
-    /**
-     * Called by the Android system to decide if a screen can be autofilled by the callback.
-     *
-     * @param inlineSuggestionsRequest the {@link InlineSuggestionsRequest request} to handle if
-     *     currently inline suggestions are supported and can be displayed.
-     * @param cancellationSignal signal for observing cancellation requests. The system will use
-     *     this to notify you that the fill result is no longer needed and you should stop
-     *     handling this fill request in order to save resources.
-     * @param callback object used to notify the result of the request.
-     */
-    void onFillRequest(@Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
-            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
-}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 2e5967c..51afe4c 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -24,11 +24,9 @@
 import android.content.IntentSender;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.service.autofill.IFillCallback;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutofillWindowPresenter;
-import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.KeyEvent;
 
 import com.android.internal.os.IResultReceiver;
@@ -144,12 +142,6 @@
    void requestShowSoftInput(in AutofillId id);
 
     /**
-     * Requests to determine if a screen can be autofilled by the client app.
-     */
-    void requestFillFromClient(int id, in InlineSuggestionsRequest request,
-            in IFillCallback callback);
-
-    /**
      * Notifies autofill ids that require to show the fill dialog.
      */
     void notifyFillDialogTriggerIds(in List<AutofillId> ids);
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 77a2b5b..581feca 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -112,22 +112,6 @@
     private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
 
     /**
-     * Whether the IME supports inline suggestions from the default Autofill service that
-     * provides the input view.
-     *
-     * Note: The default value is {@code true}.
-     */
-    private boolean mServiceSupported;
-
-    /**
-     * Whether the IME supports inline suggestions from the application that provides the
-     * input view.
-     *
-     * Note: The default value is {@code true}.
-     */
-    private boolean mClientSupported;
-
-    /**
      * @hide
      * @see {@link #mHostInputToken}.
      */
@@ -221,15 +205,9 @@
         return Bundle.EMPTY;
     }
 
-    private static boolean defaultServiceSupported() {
-        return true;
-    }
-
-    private static boolean defaultClientSupported() {
-        return true;
-    }
-
-    /** @hide */
+    /**
+     * @hide
+     */
     abstract static class BaseBuilder {
         abstract Builder setInlinePresentationSpecs(
                 @NonNull List<android.widget.inline.InlinePresentationSpec> specs);
@@ -241,25 +219,14 @@
         abstract Builder setHostDisplayId(int value);
     }
 
-    /** @hide */
-    public boolean isServiceSupported() {
-        return mServiceSupported;
-    }
 
-    /** @hide */
-    public boolean isClientSupported() {
-        return mClientSupported;
-    }
-
-
-
-    // Code below generated by codegen v1.0.22.
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+    // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -275,9 +242,7 @@
             @NonNull Bundle extras,
             @Nullable IBinder hostInputToken,
             int hostDisplayId,
-            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec,
-            boolean serviceSupported,
-            boolean clientSupported) {
+            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec) {
         this.mMaxSuggestionCount = maxSuggestionCount;
         this.mInlinePresentationSpecs = inlinePresentationSpecs;
         com.android.internal.util.AnnotationValidations.validate(
@@ -294,8 +259,6 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
-        this.mServiceSupported = serviceSupported;
-        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -379,9 +342,7 @@
     }
 
     /**
-     * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
-     *
-     * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)
+     * Specifies the UI specification for the inline suggestion tooltip in the response.
      */
     @DataClass.Generated.Member
     public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() {
@@ -402,9 +363,7 @@
                 "extras = " + mExtras + ", " +
                 "hostInputToken = " + mHostInputToken + ", " +
                 "hostDisplayId = " + mHostDisplayId + ", " +
-                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " +
-                "serviceSupported = " + mServiceSupported + ", " +
-                "clientSupported = " + mClientSupported +
+                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec +
         " }";
     }
 
@@ -428,9 +387,7 @@
                 && extrasEquals(that.mExtras)
                 && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
                 && mHostDisplayId == that.mHostDisplayId
-                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec)
-                && mServiceSupported == that.mServiceSupported
-                && mClientSupported == that.mClientSupported;
+                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec);
     }
 
     @Override
@@ -448,8 +405,6 @@
         _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
         _hash = 31 * _hash + mHostDisplayId;
         _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec);
-        _hash = 31 * _hash + Boolean.hashCode(mServiceSupported);
-        _hash = 31 * _hash + Boolean.hashCode(mClientSupported);
         return _hash;
     }
 
@@ -460,8 +415,6 @@
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
         int flg = 0;
-        if (mServiceSupported) flg |= 0x100;
-        if (mClientSupported) flg |= 0x200;
         if (mHostInputToken != null) flg |= 0x20;
         if (mInlineTooltipPresentationSpec != null) flg |= 0x80;
         dest.writeInt(flg);
@@ -487,11 +440,9 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         int flg = in.readInt();
-        boolean serviceSupported = (flg & 0x100) != 0;
-        boolean clientSupported = (flg & 0x200) != 0;
         int maxSuggestionCount = in.readInt();
         List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
-        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class);
+        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
         String hostPackageName = in.readString();
         LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR);
         Bundle extras = in.readBundle();
@@ -515,8 +466,6 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
-        this.mServiceSupported = serviceSupported;
-        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -550,8 +499,6 @@
         private @Nullable IBinder mHostInputToken;
         private int mHostDisplayId;
         private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
-        private boolean mServiceSupported;
-        private boolean mClientSupported;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -684,9 +631,7 @@
         }
 
         /**
-         * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
-         *
-         * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s
+         * Specifies the UI specification for the inline suggestion tooltip in the response.
          */
         @DataClass.Generated.Member
         public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) {
@@ -696,44 +641,10 @@
             return this;
         }
 
-        /**
-         * Whether the IME supports inline suggestions from the default Autofill service that
-         * provides the input view.
-         *
-         * Note: The default value is {@code true}.
-         *
-         * @hide
-         */
-        @TestApi
-        @DataClass.Generated.Member
-        public @NonNull Builder setServiceSupported(boolean value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x100;
-            mServiceSupported = value;
-            return this;
-        }
-
-        /**
-         * Whether the IME supports inline suggestions from the application that provides the
-         * input view.
-         *
-         * Note: The default value is {@code true}.
-         *
-         * @hide
-         */
-        @TestApi
-        @DataClass.Generated.Member
-        public @NonNull Builder setClientSupported(boolean value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x200;
-            mClientSupported = value;
-            return this;
-        }
-
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull InlineSuggestionsRequest build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x400; // Mark builder used
+            mBuilderFieldsSet |= 0x100; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mMaxSuggestionCount = defaultMaxSuggestionCount();
@@ -756,12 +667,6 @@
             if ((mBuilderFieldsSet & 0x80) == 0) {
                 mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec();
             }
-            if ((mBuilderFieldsSet & 0x100) == 0) {
-                mServiceSupported = defaultServiceSupported();
-            }
-            if ((mBuilderFieldsSet & 0x200) == 0) {
-                mClientSupported = defaultClientSupported();
-            }
             InlineSuggestionsRequest o = new InlineSuggestionsRequest(
                     mMaxSuggestionCount,
                     mInlinePresentationSpecs,
@@ -770,14 +675,12 @@
                     mExtras,
                     mHostInputToken,
                     mHostDisplayId,
-                    mInlineTooltipPresentationSpec,
-                    mServiceSupported,
-                    mClientSupported);
+                    mInlineTooltipPresentationSpec);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x400) != 0) {
+            if ((mBuilderFieldsSet & 0x100) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -785,10 +688,10 @@
     }
 
     @DataClass.Generated(
-            time = 1615798784918L,
-            codegenVersion = "1.0.22",
+            time = 1682382296877L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
-            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate  boolean mServiceSupported\nprivate  boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static  boolean defaultServiceSupported()\nprivate static  boolean defaultClientSupported()\npublic  boolean isServiceSupported()\npublic  boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 8f270f5..62f3c90 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -412,4 +412,32 @@
     public boolean setImeConsumesInput(boolean imeConsumesInput) {
         return mTarget.setImeConsumesInput(imeConsumesInput);
     }
+
+    /**
+     * Called by the system when it needs to take a snapshot of multiple text-related data in an
+     * atomic manner.
+     *
+     * <p><strong>Editor authors</strong>: Supporting this method is strongly encouraged. Atomically
+     * taken {@link TextSnapshot} is going to be really helpful for the system when optimizing IPCs
+     * in a safe and deterministic manner.  Return {@code null} if an atomically taken
+     * {@link TextSnapshot} is unavailable.  The system continues supporting such a scenario
+     * gracefully.</p>
+     *
+     * <p><strong>IME authors</strong>: Currently IMEs cannot call this method directly and always
+     * receive {@code null} as the result.</p>
+     *
+     * <p>Beware that there is a bug that this method was not overridden in
+     * {@link InputConnectionWrapper}, which ended up always returning {@code null} when gets
+     * called even if the wrapped {@link InputConnection} implements this method.  The bug was
+     * fixed in {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}.</p>
+     *
+     * @return {@code null} if {@link TextSnapshot} is unavailable and/or this API is called from
+     *         IMEs. Beware the bug in older devices mentioned above.
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Nullable
+    @Override
+    public TextSnapshot takeSnapshot() {
+        return mTarget.takeSnapshot();
+    }
 }
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index 95451a9..fa7f577 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -198,17 +198,21 @@
          * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
          *
          * @param hardwareBuffer       The existing HardwareBuffer object
-         * @param namedColorSpace      Integer value of a named color space {@link ColorSpace.Named}
+         * @param dataspace            Dataspace describing the content.
+         *                             {@see android.hardware.DataSpace}
          * @param containsSecureLayers Indicates whether this graphic buffer contains captured
          *                             contents of secure layers, in which case the screenshot
          *                             should not be persisted.
          * @param containsHdrLayers    Indicates whether this graphic buffer contains HDR content.
          */
         private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer,
-                int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) {
-            ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]);
+                int dataspace, boolean containsSecureLayers, boolean containsHdrLayers) {
+            ColorSpace colorSpace = ColorSpace.getFromDataSpace(dataspace);
             return new ScreenshotHardwareBuffer(
-                    hardwareBuffer, colorSpace, containsSecureLayers, containsHdrLayers);
+                    hardwareBuffer,
+                    colorSpace != null ? colorSpace : ColorSpace.get(ColorSpace.Named.SRGB),
+                    containsSecureLayers,
+                    containsHdrLayers);
         }
 
         public ColorSpace getColorSpace() {
@@ -271,8 +275,8 @@
         public final boolean mAllowProtected;
         public final long mUid;
         public final boolean mGrayscale;
-
         final SurfaceControl[] mExcludeLayers;
+        public final boolean mHintForSeamlessTransition;
 
         private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) {
             mPixelFormat = builder.mPixelFormat;
@@ -284,6 +288,7 @@
             mUid = builder.mUid;
             mGrayscale = builder.mGrayscale;
             mExcludeLayers = builder.mExcludeLayers;
+            mHintForSeamlessTransition = builder.mHintForSeamlessTransition;
         }
 
         private CaptureArgs(Parcel in) {
@@ -305,6 +310,7 @@
             } else {
                 mExcludeLayers = null;
             }
+            mHintForSeamlessTransition = in.readBoolean();
         }
 
         /** Release any layers if set using {@link Builder#setExcludeLayers(SurfaceControl[])}. */
@@ -352,6 +358,7 @@
             private long mUid = -1;
             private boolean mGrayscale;
             private SurfaceControl[] mExcludeLayers;
+            private boolean mHintForSeamlessTransition;
 
             /**
              * Construct a new {@link CaptureArgs} with the set parameters. The builder remains
@@ -449,6 +456,21 @@
             }
 
             /**
+             * Set whether the screenshot will be used in a system animation.
+             * This hint is used for picking the "best" colorspace for the screenshot, in particular
+             * for mixing HDR and SDR content.
+             * E.g., hintForSeamlessTransition is false, then a colorspace suitable for file
+             * encoding, such as BT2100, may be chosen. Otherwise, then the display's color space
+             * would be chosen, with the possibility of having an extended brightness range. This
+             * is important for screenshots that are directly re-routed to a SurfaceControl in
+             * order to preserve accurate colors.
+             */
+            public T setHintForSeamlessTransition(boolean hintForSeamlessTransition) {
+                mHintForSeamlessTransition = hintForSeamlessTransition;
+                return getThis();
+            }
+
+            /**
              * Each sub class should return itself to allow the builder to chain properly
              */
             T getThis() {
@@ -471,7 +493,6 @@
             dest.writeBoolean(mAllowProtected);
             dest.writeLong(mUid);
             dest.writeBoolean(mGrayscale);
-
             if (mExcludeLayers != null) {
                 dest.writeInt(mExcludeLayers.length);
                 for (SurfaceControl excludeLayer : mExcludeLayers) {
@@ -480,6 +501,7 @@
             } else {
                 dest.writeInt(0);
             }
+            dest.writeBoolean(mHintForSeamlessTransition);
         }
 
         public static final Parcelable.Creator<CaptureArgs> CREATOR =
@@ -627,6 +649,7 @@
                 setUid(args.mUid);
                 setGrayscale(args.mGrayscale);
                 setExcludeLayers(args.mExcludeLayers);
+                setHintForSeamlessTransition(args.mHintForSeamlessTransition);
             }
 
             public Builder(SurfaceControl layer) {
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 51382a4..4d0132e 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -21,6 +21,8 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -421,36 +423,45 @@
                 return false;
             }
 
-            boolean requestsPredictiveBack;
+            boolean requestsPredictiveBack = false;
 
             // Check if the context is from an activity.
             while ((context instanceof ContextWrapper) && !(context instanceof Activity)) {
                 context = ((ContextWrapper) context).getBaseContext();
             }
 
+            boolean shouldCheckActivity = false;
+
             if (context instanceof Activity) {
                 final Activity activity = (Activity) context;
 
-                if (activity.getActivityInfo().hasOnBackInvokedCallbackEnabled()) {
-                    requestsPredictiveBack =
-                            activity.getActivityInfo().isOnBackInvokedCallbackEnabled();
-                } else {
-                    requestsPredictiveBack =
-                            context.getApplicationInfo().isOnBackInvokedCallbackEnabled();
-                }
+                final ActivityInfo activityInfo = activity.getActivityInfo();
+                if (activityInfo != null) {
+                    if (activityInfo.hasOnBackInvokedCallbackEnabled()) {
+                        shouldCheckActivity = true;
+                        requestsPredictiveBack = activityInfo.isOnBackInvokedCallbackEnabled();
 
-                if (DEBUG) {
-                    Log.d(TAG, TextUtils.formatSimple("Activity: %s isPredictiveBackEnabled=%s",
-                            activity.getComponentName(),
-                            requestsPredictiveBack));
+                        if (DEBUG) {
+                            Log.d(TAG, TextUtils.formatSimple(
+                                    "Activity: %s isPredictiveBackEnabled=%s",
+                                    activity.getComponentName(),
+                                    requestsPredictiveBack));
+                        }
+                    }
+                } else {
+                    Log.w(TAG, "The ActivityInfo is null, so we cannot verify if this Activity"
+                            + " has the 'android:enableOnBackInvokedCallback' attribute."
+                            + " The application attribute will be used as a fallback.");
                 }
-            } else {
-                requestsPredictiveBack =
-                        context.getApplicationInfo().isOnBackInvokedCallbackEnabled();
+            }
+
+            if (!shouldCheckActivity) {
+                final ApplicationInfo applicationInfo = context.getApplicationInfo();
+                requestsPredictiveBack = applicationInfo.isOnBackInvokedCallbackEnabled();
 
                 if (DEBUG) {
                     Log.d(TAG, TextUtils.formatSimple("App: %s requestsPredictiveBack=%s",
-                            context.getApplicationInfo().packageName,
+                            applicationInfo.packageName,
                             requestsPredictiveBack));
                 }
             }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 853fe2f..86c2893 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -76,7 +76,7 @@
 
         /** Gating the removal of sorting-notifications-by-interruptiveness. */
         public static final Flag NO_SORT_BY_INTERRUPTIVENESS =
-                devFlag("persist.sysui.notification.no_sort_by_interruptiveness");
+                releasedFlag("persist.sysui.notification.no_sort_by_interruptiveness");
 
         /** Gating the logging of DND state change events. */
         public static final Flag LOG_DND_STATE_EVENTS =
@@ -115,7 +115,7 @@
     }
 
     /**
-     * Creates a flag that is enabled by default in debuggable builds.
+     * Creates a flag that is disabled by default in debuggable builds.
      * It can be enabled by setting this flag's SystemProperty to 1.
      *
      * This flag is ALWAYS disabled in release builds.
diff --git a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
deleted file mode 100644
index 39d8380..0000000
--- a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
+++ /dev/null
@@ -1,92 +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.internal.display;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.Display;
-
-/**
- * Constants and utility methods for refresh rate settings.
- */
-public class RefreshRateSettingsUtils {
-
-    private static final String TAG = "RefreshRateSettingsUtils";
-
-    public static final float DEFAULT_REFRESH_RATE = 60f;
-
-    /**
-     * Find the highest refresh rate among all the modes of the default display.
-     * @param context The context
-     * @return The highest refresh rate
-     */
-    public static float findHighestRefreshRateForDefaultDisplay(Context context) {
-        final DisplayManager dm = context.getSystemService(DisplayManager.class);
-        final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
-
-        if (display == null) {
-            Log.w(TAG, "No valid default display device");
-            return DEFAULT_REFRESH_RATE;
-        }
-
-        float maxRefreshRate = DEFAULT_REFRESH_RATE;
-        for (Display.Mode mode : display.getSupportedModes()) {
-            if (Math.round(mode.getRefreshRate()) > maxRefreshRate) {
-                maxRefreshRate = mode.getRefreshRate();
-            }
-        }
-        return maxRefreshRate;
-    }
-
-    /**
-     * Get the min refresh rate which is determined by
-     * {@link Settings.System.FORCE_PEAK_REFRESH_RATE}.
-     * @param context The context
-     * @return The min refresh rate
-     */
-    public static float getMinRefreshRate(Context context) {
-        final ContentResolver cr = context.getContentResolver();
-        int forcePeakRefreshRateSetting = Settings.System.getIntForUser(cr,
-                Settings.System.FORCE_PEAK_REFRESH_RATE, -1, cr.getUserId());
-        return forcePeakRefreshRateSetting == 1
-                ? findHighestRefreshRateForDefaultDisplay(context)
-                : 0;
-    }
-
-    /**
-     * Get the peak refresh rate which is determined by {@link Settings.System.SMOOTH_DISPLAY}.
-     * @param context The context
-     * @param defaultPeakRefreshRate The refresh rate to return if the setting doesn't have a value
-     * @return The peak refresh rate
-     */
-    public static float getPeakRefreshRate(Context context, float defaultPeakRefreshRate) {
-        final ContentResolver cr = context.getContentResolver();
-        int smoothDisplaySetting = Settings.System.getIntForUser(cr,
-                Settings.System.SMOOTH_DISPLAY, -1, cr.getUserId());
-        switch (smoothDisplaySetting) {
-            case 0:
-                return DEFAULT_REFRESH_RATE;
-            case 1:
-                return findHighestRefreshRateForDefaultDisplay(context);
-            default:
-                return defaultPeakRefreshRate;
-        }
-    }
-}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index fbad4b9..a554d0e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -731,7 +731,7 @@
         return DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION,
                 FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                /* defaultValue= */ false);
+                /* defaultValue= */ true);
     }
 
     /** Returns if the given quality maps to an alphabetic password */
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 1b67a0d..986dbe9 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -46,6 +46,7 @@
     jfieldID uid;
     jfieldID grayscale;
     jmethodID getNativeExcludeLayers;
+    jfieldID hintForSeamlessTransition;
 } gCaptureArgsClassInfo;
 
 static struct {
@@ -69,23 +70,6 @@
     jmethodID builder;
 } gScreenshotHardwareBufferClassInfo;
 
-enum JNamedColorSpace : jint {
-    // ColorSpace.Named.SRGB.ordinal() = 0;
-    SRGB = 0,
-
-    // ColorSpace.Named.DISPLAY_P3.ordinal() = 7;
-    DISPLAY_P3 = 7,
-};
-
-constexpr jint fromDataspaceToNamedColorSpaceValue(const ui::Dataspace dataspace) {
-    switch (dataspace) {
-        case ui::Dataspace::DISPLAY_P3:
-            return JNamedColorSpace::DISPLAY_P3;
-        default:
-            return JNamedColorSpace::SRGB;
-    }
-}
-
 static void checkAndClearException(JNIEnv* env, const char* methodName) {
     if (env->ExceptionCheck()) {
         ALOGE("An exception was thrown by callback '%s'.", methodName);
@@ -119,12 +103,11 @@
         captureResults.fenceResult.value()->waitForever(LOG_TAG);
         jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer(
                 env, captureResults.buffer->toAHardwareBuffer());
-        const jint namedColorSpace =
-                fromDataspaceToNamedColorSpaceValue(captureResults.capturedDataspace);
         jobject screenshotHardwareBuffer =
                 env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz,
                                             gScreenshotHardwareBufferClassInfo.builder,
-                                            jhardwareBuffer, namedColorSpace,
+                                            jhardwareBuffer,
+                                            static_cast<jint>(captureResults.capturedDataspace),
                                             captureResults.capturedSecureLayers,
                                             captureResults.capturedHdrLayers);
         checkAndClearException(env, "builder");
@@ -185,6 +168,9 @@
             captureArgs.excludeHandles.emplace(excludeObject->getHandle());
         }
     }
+    captureArgs.hintForSeamlessTransition =
+            env->GetBooleanField(captureArgsObject,
+                                 gCaptureArgsClassInfo.hintForSeamlessTransition);
 }
 
 static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
@@ -318,9 +304,10 @@
             GetFieldIDOrDie(env, captureArgsClazz, "mAllowProtected", "Z");
     gCaptureArgsClassInfo.uid = GetFieldIDOrDie(env, captureArgsClazz, "mUid", "J");
     gCaptureArgsClassInfo.grayscale = GetFieldIDOrDie(env, captureArgsClazz, "mGrayscale", "Z");
-
     gCaptureArgsClassInfo.getNativeExcludeLayers =
             GetMethodIDOrDie(env, captureArgsClazz, "getNativeExcludeLayers", "()[J");
+    gCaptureArgsClassInfo.hintForSeamlessTransition =
+            GetFieldIDOrDie(env, captureArgsClazz, "mHintForSeamlessTransition", "Z");
 
     jclass displayCaptureArgsClazz =
             FindClassOrDie(env, "android/window/ScreenCapture$DisplayCaptureArgs");
diff --git a/core/proto/android/companion/telecom.proto b/core/proto/android/companion/telecom.proto
index 9ccadbf..3a9e5ee 100644
--- a/core/proto/android/companion/telecom.proto
+++ b/core/proto/android/companion/telecom.proto
@@ -20,9 +20,9 @@
 
 option java_multiple_files = true;
 
-// Next index: 2
+// Next index: 4
 message Telecom {
-  // Next index: 5
+  // Next index: 6
   message Call {
     // UUID representing this call
     int64 id = 1;
@@ -34,6 +34,8 @@
       // Human-readable name of the app processing this call
       string app_name = 2;
       bytes app_icon = 3;
+      // Unique identifier for this app, such as a package name.
+      string app_identifier = 4;
     }
     Origin origin = 2;
 
@@ -59,9 +61,11 @@
       REJECT_AND_BLOCK = 9;
       IGNORE = 10;
     }
-    repeated Control controls_available = 4;
+    repeated Control controls = 4;
   }
 
   // The list of active calls.
   repeated Call calls = 1;
+  // The list of requested calls or call changes.
+  repeated Call requests = 2;
 }
diff --git a/core/res/res/drawable-hdpi/pointer_all_scroll.png b/core/res/res/drawable-hdpi/pointer_all_scroll.png
index 095aadc..4af84c3 100644
--- a/core/res/res/drawable-hdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-hdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
index 9388f16..c4018c8 100644
--- a/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
index ab52bff..58bb0d4 100644
--- a/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
index 1250d35..1981d41 100644
--- a/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
index 6730c7b..d4ba79a 100644
--- a/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll.png b/core/res/res/drawable-mdpi/pointer_all_scroll.png
index 3db456e..1b81d0a 100644
--- a/core/res/res/drawable-mdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-mdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
index 120e1d7..9e1f5c9 100644
--- a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
+++ b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
index 20f319a..d1b3441 100644
--- a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
index 33ef5c9..4e26371 100644
--- a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
index fe7d496..34c0c6a 100644
--- a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
index 7b2e20c..87ec184 100644
--- a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
index 95a6620..40b9c7e 100644
--- a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
index 2e2904b..6a85b49 100644
--- a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
index ae6bfed..9bd89bf 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
index 3beb1d1..5a69bbc 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll.png b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
index e9d05d5..85aa022 100644
--- a/core/res/res/drawable-xhdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
index 1fd54fb..7448339 100644
--- a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
index caf2a97..dd37f92 100644
--- a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
index 2f22640..9e031e8 100644
--- a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
index a36deb3..150d80d 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
index 6870e23..bae907a 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
index c8d6d1f..3b23143 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
index 5bfb771..a90b286 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
index 720df91..3e7f850 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
index 82b30d1..090e3ca 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_all_scroll.png b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
index 808143a..92aae72 100644
--- a/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
index 677ccad..b1e2509 100644
--- a/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
index e01aa64..2d1217c 100644
--- a/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
index e947e0e..a99fb24 100644
--- a/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
index c867247..1f065fa 100644
--- a/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/layout-television/user_switching_dialog.xml b/core/res/res/layout-television/user_switching_dialog.xml
deleted file mode 100644
index 72150e3..0000000
--- a/core/res/res/layout-television/user_switching_dialog.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 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.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/message"
-        style="?attr/textAppearanceListItem"
-        android:layout_width="match_parent"
-        android:background="@color/background_leanback_dark"
-        android:textColor="@color/primary_text_leanback_dark"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:paddingStart="?attr/dialogPreferredPadding"
-        android:paddingEnd="?attr/dialogPreferredPadding"
-        android:paddingTop="24dp"
-        android:paddingBottom="24dp" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index bc0af12..ad864b1 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -96,12 +96,12 @@
     <!-- Width of the navigation bar when it is placed vertically on the screen in car mode -->
     <dimen name="navigation_bar_width_car_mode">96dp</dimen>
     <!-- Height of notification icons in the status bar -->
-    <dimen name="status_bar_icon_size">22dip</dimen>
+    <dimen name="status_bar_icon_size">18sp</dimen>
     <!-- Desired size of system icons in status bar. -->
-    <dimen name="status_bar_system_icon_size">15dp</dimen>
+    <dimen name="status_bar_system_icon_size">15sp</dimen>
     <!-- Intrinsic size of most system icons in status bar. This is the default value that
          is used if a Drawable reports an intrinsic size of 0. -->
-    <dimen name="status_bar_system_icon_intrinsic_size">17dp</dimen>
+    <dimen name="status_bar_system_icon_intrinsic_size">17sp</dimen>
     <!-- Size of the giant number (unread count) in the notifications -->
     <dimen name="status_bar_content_number_size">48sp</dimen>
     <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. -->
@@ -330,7 +330,7 @@
     <dimen name="notification_icon_circle_start">16dp</dimen>
 
     <!-- size (width and height) of the icon in the notification header -->
-    <dimen name="notification_header_icon_size_ambient">18dp</dimen>
+    <dimen name="notification_header_icon_size_ambient">18sp</dimen>
 
     <!-- The margin before the start of the app name in the header. -->
     <dimen name="notification_header_app_name_margin_start">3dp</dimen>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 596f351..7c2759a 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1183,6 +1183,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "-1005167552": {
+      "message": "Playing #%d in parallel on track #%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "-1003678883": {
       "message": "Cleaning splash screen token=%s",
       "level": "VERBOSE",
@@ -1447,6 +1453,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "-774908272": {
+      "message": "Marking #%d animation as SYNC.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "-771177730": {
       "message": "Removing focused app token:%s displayId=%d",
       "level": "VERBOSE",
@@ -1495,12 +1507,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "-741766551": {
-      "message": "Content Recording: Ignoring session on invalid virtual display",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/ContentRecordingController.java"
-    },
     "-732715767": {
       "message": "Unable to retrieve window container to start recording for display %d",
       "level": "VERBOSE",
@@ -2017,6 +2023,12 @@
       "group": "WM_DEBUG_WALLPAPER",
       "at": "com\/android\/server\/wm\/WallpaperController.java"
     },
+    "-266707683": {
+      "message": "Moving #%d from collecting to waiting.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "-262984451": {
       "message": "Relaunch failed %s",
       "level": "INFO",
@@ -2089,6 +2101,12 @@
       "group": "WM_DEBUG_WINDOW_MOVEMENT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-186693085": {
+      "message": "Starting a Recents transition which can be parallel.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-182877285": {
       "message": "Wallpaper layer changed: assigning layers + relayout",
       "level": "VERBOSE",
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 302c72e..dd4b58e 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -40,6 +40,7 @@
 import android.graphics.drawable.NinePatchDrawable;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
+import android.media.MediaFormat;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Trace;
@@ -914,8 +915,6 @@
             case "image/jpeg":
             case "image/webp":
             case "image/gif":
-            case "image/heif":
-            case "image/heic":
             case "image/bmp":
             case "image/x-ico":
             case "image/vnd.wap.wbmp":
@@ -930,6 +929,9 @@
             case "image/x-pentax-pef":
             case "image/x-samsung-srw":
                 return true;
+            case "image/heif":
+            case "image/heic":
+                return isHevcDecoderSupported();
             case "image/avif":
                 return isP010SupportedForAV1();
             default:
@@ -2067,6 +2069,28 @@
         return decodeBitmapImpl(src, null);
     }
 
+    private static boolean sIsHevcDecoderSupported = false;
+    private static boolean sIsHevcDecoderSupportedInitialized = false;
+    private static final Object sIsHevcDecoderSupportedLock = new Object();
+
+    /*
+     * Check if HEVC decoder is supported by the device.
+     */
+    @SuppressWarnings("AndroidFrameworkCompatChange")
+    private static boolean isHevcDecoderSupported() {
+        synchronized (sIsHevcDecoderSupportedLock) {
+            if (sIsHevcDecoderSupportedInitialized) {
+                return sIsHevcDecoderSupported;
+            }
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_HEVC);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            sIsHevcDecoderSupported = mcl.findDecoderForFormat(format) != null;
+            sIsHevcDecoderSupportedInitialized = true;
+            return sIsHevcDecoderSupported;
+        }
+    }
+
     private static boolean sIsP010SupportedForAV1 = false;
     private static boolean sIsP010SupportedForHEVC = false;
     private static boolean sIsP010SupportedFlagsInitialized = false;
@@ -2105,20 +2129,20 @@
      * Checks if the device supports decoding 10-bit for the given mime type.
      */
     private static void checkP010SupportforAV1HEVC() {
-        MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
         for (MediaCodecInfo mediaCodecInfo : codecList.getCodecInfos()) {
             if (mediaCodecInfo.isEncoder()) {
                 continue;
             }
             for (String mediaType : mediaCodecInfo.getSupportedTypes()) {
-                if (mediaType.equalsIgnoreCase("video/av01")
-                        || mediaType.equalsIgnoreCase("video/hevc")) {
+                if (mediaType.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)
+                        || mediaType.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
                     MediaCodecInfo.CodecCapabilities codecCapabilities =
                         mediaCodecInfo.getCapabilitiesForType(mediaType);
                     for (int i = 0; i < codecCapabilities.colorFormats.length; ++i) {
                         if (codecCapabilities.colorFormats[i]
                             == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010) {
-                            if (mediaType.equalsIgnoreCase("video/av01")) {
+                            if (mediaType.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) {
                                 sIsP010SupportedForAV1 = true;
                             } else {
                                 sIsP010SupportedForHEVC = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
index 0f9260c..9abf0f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
@@ -38,7 +38,7 @@
 
     default void onTaskStackChanged() { }
 
-    default void onTaskProfileLocked(RunningTaskInfo taskInfo) { }
+    default void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId) { }
 
     default void onTaskDisplayChanged(int taskId, int newDisplayId) { }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
index e2106e4..d8859ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
@@ -150,8 +150,8 @@
     }
 
     @Override
-    public void onTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo) {
-        mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskInfo).sendToTarget();
+    public void onTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo, int userId) {
+        mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, userId, 0, taskInfo).sendToTarget();
     }
 
     @Override
@@ -348,8 +348,9 @@
                 case ON_TASK_PROFILE_LOCKED: {
                     final ActivityManager.RunningTaskInfo
                             info = (ActivityManager.RunningTaskInfo) msg.obj;
+                    final int userId = msg.arg1;
                     for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                        mTaskStackListeners.get(i).onTaskProfileLocked(info);
+                        mTaskStackListeners.get(i).onTaskProfileLocked(info, userId);
                     }
                     break;
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 86ea725..b9d2be2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -264,10 +264,10 @@
     /**
      * Show apps on desktop
      */
-    void showDesktopApps() {
+    void showDesktopApps(int displayId) {
         // Bring apps to front, ignoring their visibility status to always ensure they are on top.
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        bringDesktopAppsToFront(wct);
+        bringDesktopAppsToFront(displayId, wct);
 
         if (!wct.isEmpty()) {
             if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -280,12 +280,12 @@
     }
 
     /** Get number of tasks that are marked as visible */
-    int getVisibleTaskCount() {
-        return mDesktopModeTaskRepository.getVisibleTaskCount();
+    int getVisibleTaskCount(int displayId) {
+        return mDesktopModeTaskRepository.getVisibleTaskCount(displayId);
     }
 
-    private void bringDesktopAppsToFront(WindowContainerTransaction wct) {
-        final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
+    private void bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct) {
+        final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(displayId);
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
 
         final List<RunningTaskInfo> taskInfos = new ArrayList<>();
@@ -386,6 +386,7 @@
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
+        RunningTaskInfo triggerTask = request.getTriggerTask();
         // Only do anything if we are in desktop mode and opening/moving-to-front a task/app in
         // freeform
         if (!DesktopModeStatus.isActive(mContext)) {
@@ -399,16 +400,15 @@
                     WindowManager.transitTypeToString(request.getType()));
             return null;
         }
-        if (request.getTriggerTask() == null
-                || request.getTriggerTask().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+        if (triggerTask == null || triggerTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
             ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task");
             return null;
         }
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
 
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        bringDesktopAppsToFront(wct);
-        wct.reorder(request.getTriggerTask().token, true /* onTop */);
+        bringDesktopAppsToFront(triggerTask.displayId, wct);
+        wct.reorder(triggerTask.token, true /* onTop */);
 
         return wct;
     }
@@ -493,16 +493,17 @@
             mController = null;
         }
 
-        public void showDesktopApps() {
+        @Override
+        public void showDesktopApps(int displayId) {
             executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
-                    DesktopModeController::showDesktopApps);
+                    controller -> controller.showDesktopApps(displayId));
         }
 
         @Override
-        public int getVisibleTaskCount() throws RemoteException {
+        public int getVisibleTaskCount(int displayId) throws RemoteException {
             int[] result = new int[1];
             executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
-                    controller -> result[0] = controller.getVisibleTaskCount(),
+                    controller -> result[0] = controller.getVisibleTaskCount(displayId),
                     true /* blocking */
             );
             return result[0];
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 12f8ea2..00cc57f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -20,6 +20,8 @@
 import android.util.ArrayMap
 import android.util.ArraySet
 import android.util.SparseArray
+import androidx.core.util.forEach
+import androidx.core.util.keyIterator
 import androidx.core.util.valueIterator
 import java.util.concurrent.Executor
 import java.util.function.Consumer
@@ -29,14 +31,18 @@
  */
 class DesktopModeTaskRepository {
 
-    /**
-     * Set of task ids that are marked as active in desktop mode.
-     * Active tasks in desktop mode are freeform tasks that are visible or have been visible after
-     * desktop mode was activated.
-     * Task gets removed from this list when it vanishes. Or when desktop mode is turned off.
-     */
-    private val activeTasks = ArraySet<Int>()
-    private val visibleTasks = ArraySet<Int>()
+    /** Task data that is tracked per display */
+    private data class DisplayData(
+        /**
+         * Set of task ids that are marked as active in desktop mode. Active tasks in desktop mode
+         * are freeform tasks that are visible or have been visible after desktop mode was
+         * activated. Task gets removed from this list when it vanishes. Or when desktop mode is
+         * turned off.
+         */
+        val activeTasks: ArraySet<Int> = ArraySet(),
+        val visibleTasks: ArraySet<Int> = ArraySet(),
+    )
+
     // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
     private val freeformTasksInZOrder = mutableListOf<Int>()
     private val activeTasksListeners = ArraySet<ActiveTasksListener>()
@@ -47,9 +53,22 @@
     private var desktopGestureExclusionListener: Consumer<Region>? = null
     private var desktopGestureExclusionExecutor: Executor? = null
 
-    /**
-     * Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository.
-     */
+    private val displayData =
+        object : SparseArray<DisplayData>() {
+            /**
+             * Get the [DisplayData] associated with this [displayId]
+             *
+             * Creates a new instance if one does not exist
+             */
+            fun getOrCreate(displayId: Int): DisplayData {
+                if (!contains(displayId)) {
+                    put(displayId, DisplayData())
+                }
+                return get(displayId)
+            }
+        }
+
+    /** Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository. */
     fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) {
         activeTasksListeners.add(activeTasksListener)
     }
@@ -57,10 +76,17 @@
     /**
      * Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not.
      */
-    fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
-        visibleTasksListeners.put(visibleTasksListener, executor)
-        executor.execute(
-                Runnable { visibleTasksListener.onVisibilityChanged(visibleTasks.size > 0) })
+    fun addVisibleTasksListener(
+        visibleTasksListener: VisibleTasksListener,
+        executor: Executor
+    ) {
+        visibleTasksListeners[visibleTasksListener] = executor
+        displayData.keyIterator().forEach { displayId ->
+            val visibleTasks = getVisibleTaskCount(displayId)
+            executor.execute {
+                visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0)
+            }
+        }
     }
 
     /**
@@ -100,14 +126,21 @@
     }
 
     /**
-     * Mark a task with given [taskId] as active.
+     * Mark a task with given [taskId] as active on given [displayId]
      *
-     * @return `true` if the task was not active
+     * @return `true` if the task was not active on given [displayId]
      */
-    fun addActiveTask(taskId: Int): Boolean {
-        val added = activeTasks.add(taskId)
+    fun addActiveTask(displayId: Int, taskId: Int): Boolean {
+        // Check if task is active on another display, if so, remove it
+        displayData.forEach { id, data ->
+            if (id != displayId && data.activeTasks.remove(taskId)) {
+                activeTasksListeners.onEach { it.onActiveTasksChanged(id) }
+            }
+        }
+
+        val added = displayData.getOrCreate(displayId).activeTasks.add(taskId)
         if (added) {
-            activeTasksListeners.onEach { it.onActiveTasksChanged() }
+            activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
         }
         return added
     }
@@ -118,65 +151,93 @@
      * @return `true` if the task was active
      */
     fun removeActiveTask(taskId: Int): Boolean {
-        val removed = activeTasks.remove(taskId)
-        if (removed) {
-            activeTasksListeners.onEach { it.onActiveTasksChanged() }
+        var result = false
+        displayData.forEach { displayId, data ->
+            if (data.activeTasks.remove(taskId)) {
+                activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
+                result = true
+            }
         }
-        return removed
+        return result
     }
 
     /**
      * Check if a task with the given [taskId] was marked as an active task
      */
     fun isActiveTask(taskId: Int): Boolean {
-        return activeTasks.contains(taskId)
+        return displayData.valueIterator().asSequence().any { data ->
+            data.activeTasks.contains(taskId)
+        }
     }
 
     /**
      * Whether a task is visible.
      */
     fun isVisibleTask(taskId: Int): Boolean {
-        return visibleTasks.contains(taskId)
+        return displayData.valueIterator().asSequence().any { data ->
+            data.visibleTasks.contains(taskId)
+        }
     }
 
     /**
-     * Get a set of the active tasks
+     * Get a set of the active tasks for given [displayId]
      */
-    fun getActiveTasks(): ArraySet<Int> {
-        return ArraySet(activeTasks)
+    fun getActiveTasks(displayId: Int): ArraySet<Int> {
+        return ArraySet(displayData[displayId]?.activeTasks)
     }
 
     /**
      * Get a list of freeform tasks, ordered from top-bottom (top at index 0).
      */
+     // TODO(b/278084491): pass in display id
     fun getFreeformTasksInZOrder(): List<Int> {
         return freeformTasksInZOrder
     }
 
     /**
      * Updates whether a freeform task with this id is visible or not and notifies listeners.
+     *
+     * If the task was visible on a different display with a different displayId, it is removed from
+     * the set of visible tasks on that display. Listeners will be notified.
      */
-    fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) {
-        val prevCount: Int = visibleTasks.size
+    fun updateVisibleFreeformTasks(displayId: Int, taskId: Int, visible: Boolean) {
         if (visible) {
-            visibleTasks.add(taskId)
-        } else {
-            visibleTasks.remove(taskId)
-        }
-        if (prevCount == 0 && visibleTasks.size == 1 ||
-                prevCount > 0 && visibleTasks.size == 0) {
-            for ((listener, executor) in visibleTasksListeners) {
-                executor.execute(
-                        Runnable { listener.onVisibilityChanged(visibleTasks.size > 0) })
+            // Task is visible. Check if we need to remove it from any other display.
+            val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId }
+            for (otherDisplayId in otherDisplays) {
+                if (displayData[otherDisplayId].visibleTasks.remove(taskId)) {
+                    // Task removed from other display, check if we should notify listeners
+                    if (displayData[otherDisplayId].visibleTasks.isEmpty()) {
+                        notifyVisibleTaskListeners(otherDisplayId, hasVisibleFreeformTasks = false)
+                    }
+                }
             }
         }
+
+        val prevCount = getVisibleTaskCount(displayId)
+        if (visible) {
+            displayData.getOrCreate(displayId).visibleTasks.add(taskId)
+        } else {
+            displayData[displayId]?.visibleTasks?.remove(taskId)
+        }
+        val newCount = getVisibleTaskCount(displayId)
+        // Check if count changed and if there was no tasks or this is the first task
+        if (prevCount != newCount && (prevCount == 0 || newCount == 0)) {
+            notifyVisibleTaskListeners(displayId, newCount > 0)
+        }
+    }
+
+    private fun notifyVisibleTaskListeners(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+        visibleTasksListeners.forEach { (listener, executor) ->
+            executor.execute { listener.onVisibilityChanged(displayId, hasVisibleFreeformTasks) }
+        }
     }
 
     /**
-     * Get number of tasks that are marked as visible
+     * Get number of tasks that are marked as visible on given [displayId]
      */
-    fun getVisibleTaskCount(): Int {
-        return visibleTasks.size
+    fun getVisibleTaskCount(displayId: Int): Int {
+        return displayData[displayId]?.visibleTasks?.size ?: 0
     }
 
     /**
@@ -226,7 +287,7 @@
          * Called when the active tasks change in desktop mode.
          */
         @JvmDefault
-        fun onActiveTasksChanged() {}
+        fun onActiveTasksChanged(displayId: Int) {}
     }
 
     /**
@@ -237,6 +298,6 @@
          * Called when the desktop starts or stops showing freeform tasks.
          */
         @JvmDefault
-        fun onVisibilityChanged(hasVisibleFreeformTasks: Boolean) {}
+        fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
     }
 }
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 0d56023..c814fe5 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
@@ -97,10 +97,11 @@
     }
 
     /** Show all tasks, that are part of the desktop, on top of launcher */
-    fun showDesktopApps() {
+    fun showDesktopApps(displayId: Int) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps")
         val wct = WindowContainerTransaction()
-        bringDesktopAppsToFront(wct)
+        // TODO(b/278084491): pass in display id
+        bringDesktopAppsToFront(displayId, wct)
 
         // Execute transaction if there are pending operations
         if (!wct.isEmpty) {
@@ -114,8 +115,8 @@
     }
 
     /** Get number of tasks that are marked as visible */
-    fun getVisibleTaskCount(): Int {
-        return desktopModeTaskRepository.getVisibleTaskCount()
+    fun getVisibleTaskCount(displayId: Int): Int {
+        return desktopModeTaskRepository.getVisibleTaskCount(displayId)
     }
 
     /** Move a task with given `taskId` to desktop */
@@ -129,7 +130,7 @@
 
         val wct = WindowContainerTransaction()
         // Bring other apps to front first
-        bringDesktopAppsToFront(wct)
+        bringDesktopAppsToFront(task.displayId, wct)
         addMoveToDesktopChanges(wct, task.token)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
@@ -165,7 +166,7 @@
             freeformBounds: Rect
     ) {
         val wct = WindowContainerTransaction()
-        bringDesktopAppsToFront(wct)
+        bringDesktopAppsToFront(taskInfo.displayId, wct)
         addMoveToDesktopChanges(wct, taskInfo.getToken())
         wct.setBounds(taskInfo.token, freeformBounds)
 
@@ -244,9 +245,9 @@
             ?: WINDOWING_MODE_UNDEFINED
     }
 
-    private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) {
+    private fun bringDesktopAppsToFront(displayId: Int, wct: WindowContainerTransaction) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront")
-        val activeTasks = desktopModeTaskRepository.getActiveTasks()
+        val activeTasks = desktopModeTaskRepository.getActiveTasks(displayId)
 
         // First move home to front and then other tasks on top of it
         moveHomeTaskToFront(wct)
@@ -290,18 +291,17 @@
         request: TransitionRequestInfo
     ): WindowContainerTransaction? {
         // Check if we should skip handling this transition
-        val task: RunningTaskInfo? = request.triggerTask
         val shouldHandleRequest =
             when {
                 // Only handle open or to front transitions
                 request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false
                 // Only handle when it is a task transition
-                task == null -> false
+                request.triggerTask == null -> false
                 // Only handle standard type tasks
-                task.activityType != ACTIVITY_TYPE_STANDARD -> false
+                request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> false
                 // Only handle fullscreen or freeform tasks
-                task.windowingMode != WINDOWING_MODE_FULLSCREEN &&
-                    task.windowingMode != WINDOWING_MODE_FREEFORM -> false
+                request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
+                        request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> false
                 // Otherwise process it
                 else -> true
             }
@@ -310,10 +310,11 @@
             return null
         }
 
-        val activeTasks = desktopModeTaskRepository.getActiveTasks()
+        val task: RunningTaskInfo = request.triggerTask
+        val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
 
         // Check if we should switch a fullscreen task to freeform
-        if (task?.windowingMode == WINDOWING_MODE_FULLSCREEN) {
+        if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) {
             // If there are any visible desktop tasks, switch the task to freeform
             if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
                 ProtoLog.d(
@@ -329,7 +330,7 @@
         }
 
         // CHeck if we should switch a freeform task to fullscreen
-        if (task?.windowingMode == WINDOWING_MODE_FREEFORM) {
+        if (task.windowingMode == WINDOWING_MODE_FREEFORM) {
             // If no visible desktop tasks, switch this task to freeform as the transition came
             // outside of this controller
             if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
@@ -559,20 +560,19 @@
             controller = null
         }
 
-        override fun showDesktopApps() {
+        override fun showDesktopApps(displayId: Int) {
             ExecutorUtils.executeRemoteCallWithTaskPermission(
                 controller,
-                "showDesktopApps",
-                Consumer(DesktopTasksController::showDesktopApps)
-            )
+                "showDesktopApps"
+            ) { c -> c.showDesktopApps(displayId) }
         }
 
-        override fun getVisibleTaskCount(): Int {
+        override fun getVisibleTaskCount(displayId: Int): Int {
             val result = IntArray(1)
             ExecutorUtils.executeRemoteCallWithTaskPermission(
                 controller,
                 "getVisibleTaskCount",
-                { controller -> result[0] = controller.getVisibleTaskCount() },
+                { controller -> result[0] = controller.getVisibleTaskCount(displayId) },
                 true /* blocking */
             )
             return result[0]
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index d0739e1..899d672 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -21,9 +21,9 @@
  */
 interface IDesktopMode {
 
-    /** Show apps on the desktop */
-    void showDesktopApps();
+    /** Show apps on the desktop on the given display */
+    void showDesktopApps(int displayId);
 
-    /** Get count of visible desktop tasks */
-    int getVisibleTaskCount();
+    /** Get count of visible desktop tasks on the given display */
+    int getVisibleTaskCount(int displayId);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 48487bc..22541bbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -94,11 +94,12 @@
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
                 if (taskInfo.isVisible) {
-                    if (repository.addActiveTask(taskInfo.taskId)) {
+                    if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                                 "Adding active freeform task: #%d", taskInfo.taskId);
                     }
-                    repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
+                    repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId,
+                            true);
                 }
             });
         }
@@ -117,7 +118,7 @@
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                             "Removing active freeform task: #%d", taskInfo.taskId);
                 }
-                repository.updateVisibleFreeformTasks(taskInfo.taskId, false);
+                repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, false);
             });
         }
 
@@ -137,12 +138,13 @@
         if (DesktopModeStatus.isAnyEnabled()) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 if (taskInfo.isVisible) {
-                    if (repository.addActiveTask(taskInfo.taskId)) {
+                    if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                                 "Adding active freeform task: #%d", taskInfo.taskId);
                     }
                 }
-                repository.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible);
+                repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId,
+                        taskInfo.isVisible);
             });
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index e1a56a1..6b6a7bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -153,6 +153,8 @@
 
     @Override
     public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
+        mWindowDecorViewModel.onTransitionMerged(merged, playing);
+
         final List<ActivityManager.RunningTaskInfo> infoOfMerged =
                 mTransitionToTaskInfo.get(merged);
         if (infoOfMerged == null) {
@@ -169,8 +171,6 @@
         } else {
             mTransitionToTaskInfo.put(playing, infoOfMerged);
         }
-
-        mWindowDecorViewModel.onTransitionMerged(merged, playing);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 566c130..d3e7f9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -72,7 +72,6 @@
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -139,17 +138,6 @@
     // the runnable to execute after WindowContainerTransactions is applied to finish resizing pip
     private Runnable mPipFinishResizeWCTRunnable;
 
-    private final WindowContainerTransactionCallback mPipFinishResizeWCTCallback =
-            new WindowContainerTransactionCallback() {
-        @Override
-        public void onTransactionReady(int id, SurfaceControl.Transaction t) {
-            t.apply();
-
-            // execute the runnable if non-null after WCT is applied to finish resizing pip
-            maybePerformFinishResizeCallback();
-        }
-    };
-
     private void maybePerformFinishResizeCallback() {
         if (mPipFinishResizeWCTRunnable != null) {
             mPipFinishResizeWCTRunnable.run();
@@ -175,6 +163,7 @@
             final int direction = animator.getTransitionDirection();
             if (mIsCancelled) {
                 sendOnPipTransitionFinished(direction);
+                maybePerformFinishResizeCallback();
                 return;
             }
             final int animationType = animator.getAnimationType();
@@ -199,6 +188,10 @@
                     || isRemovePipDirection(direction);
             if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
                     || isExitPipDirection) {
+                // execute the finish resize callback if needed after the transaction is committed
+                tx.addTransactionCommittedListener(mMainExecutor,
+                        PipTaskOrganizer.this::maybePerformFinishResizeCallback);
+
                 // Finish resize as long as we're not exiting PIP, or, if we are, only if this is
                 // the end of an exit PIP animation.
                 // This is necessary in case there was a resize animation ongoing when exit PIP
@@ -1614,12 +1607,8 @@
         if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
             mSplitScreenOptional.ifPresent(splitScreenController ->
                     splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct));
-        } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) {
-            // when leaving PiP we can call the callback without sync
-            maybePerformFinishResizeCallback();
-            mTaskOrganizer.applyTransaction(wct);
         } else {
-            mTaskOrganizer.applySyncTransaction(wct, mPipFinishResizeWCTCallback);
+            mTaskOrganizer.applyTransaction(wct);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index b0bb14b..f8e1435 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -1060,13 +1060,22 @@
     /** Save the state to restore to on re-entry. */
     public void saveReentryState(Rect pipBounds) {
         float snapFraction = mPipBoundsAlgorithm.getSnapFraction(pipBounds);
-        if (mPipBoundsState.hasUserResizedPip()) {
-            final Rect reentryBounds = mTouchHandler.getUserResizeBounds();
-            final Size reentrySize = new Size(reentryBounds.width(), reentryBounds.height());
-            mPipBoundsState.saveReentryState(reentrySize, snapFraction);
-        } else {
+
+        if (!mPipBoundsState.hasUserResizedPip()) {
             mPipBoundsState.saveReentryState(null /* bounds */, snapFraction);
+            return;
         }
+
+        Size reentrySize = new Size(pipBounds.width(), pipBounds.height());
+
+        // TODO: b/279937014 Investigate why userResizeBounds are empty with shell transitions on
+        // fallback to using the userResizeBounds if userResizeBounds are not empty
+        if (!mTouchHandler.getUserResizeBounds().isEmpty()) {
+            Rect userResizeBounds = mTouchHandler.getUserResizeBounds();
+            reentrySize = new Size(userResizeBounds.width(), userResizeBounds.height());
+        }
+
+        mPipBoundsState.saveReentryState(reentrySize, snapFraction);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 956af70..281cae5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -104,6 +104,7 @@
     private boolean mAllowGesture;
     private boolean mIsAttached;
     private boolean mIsEnabled;
+    private boolean mEnableTouch;
     private boolean mEnablePinchResize;
     private boolean mEnableDragCornerResize;
     private boolean mIsSysUiStateValid;
@@ -138,6 +139,7 @@
         mPhonePipMenuController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
         mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
+        mEnableTouch = true;
 
         mUpdateResizeBoundsCallback = (rect) -> {
             mUserResizeBounds.set(rect);
@@ -248,6 +250,11 @@
             return;
         }
 
+        if (!mEnableTouch) {
+            // No need to handle anything if touches are not enabled for resizing.
+            return;
+        }
+
         // Don't allow resize when PiP is stashed.
         if (mPipBoundsState.isStashed()) {
             return;
@@ -581,14 +588,13 @@
                         mLastResizeBounds, movementBounds);
                 mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
 
-                // disable the pinch resizing until the final bounds are updated
-                final boolean prevEnablePinchResize = mEnablePinchResize;
-                mEnablePinchResize = false;
+                // disable the resizing until the final bounds are updated
+                mEnableTouch = false;
 
                 mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
                         PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
                             // reset the pinch resizing to its default state
-                            mEnablePinchResize = prevEnablePinchResize;
+                            mEnableTouch = true;
                         });
             } else {
                 mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index c5bfd87..5c9709c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -245,7 +245,7 @@
     }
 
     @Override
-    public void onActiveTasksChanged() {
+    public void onActiveTasksChanged(int displayId) {
         notifyRecentTasksChanged();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 7991c52..2ab4c75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -82,6 +82,10 @@
         mGuard.open("release");
     }
 
+    SurfaceControl getSurfaceControl() {
+        return mSurfaceControl;
+    }
+
     /**
      * Sets the provided {@link TaskViewBase}, which is used to notify the client part about the
      * task related changes and getting the current bounds.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 81d69a4..c7e534a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -336,6 +336,19 @@
                 tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction,
                         chg.getTaskInfo(), chg.getLeash(), wct);
                 changesHandled++;
+            } else if (chg.getMode() == TRANSIT_CHANGE) {
+                TaskViewTaskController tv = findTaskView(chg.getTaskInfo());
+                if (tv == null) {
+                    if (pending != null) {
+                        Slog.w(TAG, "Found a non-TaskView task in a TaskView Transition. This "
+                                + "shouldn't happen, so there may be a visual artifact: "
+                                + chg.getTaskInfo().taskId);
+                    }
+                    continue;
+                }
+                startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
+                finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
+                changesHandled++;
             }
         }
         if (stillNeedsMatchingLaunch) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index d25318d..9ce2209 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -144,6 +144,7 @@
                                 .setCaptureSecureLayers(true)
                                 .setAllowProtected(true)
                                 .setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight))
+                                .setHintForSeamlessTransition(true)
                                 .build();
                 ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
                         ScreenCapture.captureLayers(args);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
index 9d7c39f..ba364f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
@@ -73,7 +73,8 @@
         if (mHandlerIds.containsKey(handler)) {
             handlerId = mHandlerIds.get(handler);
         } else {
-            handlerId = mHandlerIds.size();
+            // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
+            handlerId = mHandlerIds.size() + 1;
             mHandlerIds.put(handler, handlerId);
         }
 
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 4ef3350..4cda571 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
@@ -113,11 +113,6 @@
     private boolean mDragToDesktopAnimationStarted;
     private float mCaptionDragStartX;
 
-    // These values keep track of any transitions to freeform to stop relayout from running on
-    // changing task so that shellTransitions has a chance to animate the transition
-    private int mPauseRelayoutForTask = -1;
-    private IBinder mTransitionPausingRelayout;
-
     public DesktopModeWindowDecorViewModel(
             Context context,
             Handler mainHandler,
@@ -195,22 +190,25 @@
             @NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change) {
         if (change.getMode() == WindowManager.TRANSIT_CHANGE
-                && info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE) {
-            mTransitionPausingRelayout = transition;
+                && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE)) {
+            mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
+                    .addTransitionPausingRelayout(transition);
         }
     }
 
     @Override
     public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
-        if (merged.equals(mTransitionPausingRelayout)) {
-            mTransitionPausingRelayout = playing;
+        for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
+            final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+            decor.mergeTransitionPausingRelayout(merged, playing);
         }
     }
 
     @Override
     public void onTransitionFinished(@NonNull IBinder transition) {
-        if (transition.equals(mTransitionPausingRelayout)) {
-            mPauseRelayoutForTask = -1;
+        for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
+            final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+            decor.removeTransitionPausingRelayout(transition);
         }
     }
 
@@ -225,12 +223,7 @@
             incrementEventReceiverTasks(taskInfo.displayId);
         }
 
-        // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
-        // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
-        // and interferes with the transition animation that is playing at the same time.
-        if (taskInfo.taskId != mPauseRelayoutForTask) {
-            decoration.relayout(taskInfo);
-        }
+        decoration.relayout(taskInfo);
     }
 
     @Override
@@ -555,8 +548,7 @@
                             relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > 2 * statusBarHeight) {
                         if (DesktopModeStatus.isProto2Enabled()) {
-                            mPauseRelayoutForTask = relevantDecor.mTaskInfo.taskId;
-                            centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
+                            animateToDesktop(relevantDecor, ev);
                         } else if (DesktopModeStatus.isProto1Enabled()) {
                             mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                         }
@@ -632,6 +624,15 @@
     }
 
     /**
+     * Blocks relayout until transition is finished and transitions to Desktop
+     */
+    private void animateToDesktop(DesktopModeWindowDecoration relevantDecor,
+            MotionEvent ev) {
+        relevantDecor.incrementRelayoutBlock();
+        centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
+    }
+
+    /**
      * Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
      * @param relevantDecor the window decor of the task to be animated
      * @param ev the motion event that triggers the animation
@@ -658,10 +659,9 @@
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                mDesktopTasksController.ifPresent(
-                        c -> c.onDragPositioningEndThroughStatusBar(
-                                relevantDecor.mTaskInfo,
-                                calculateFreeformBounds(FINAL_FREEFORM_SCALE)));
+                mDesktopTasksController.ifPresent(c ->
+                        c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo,
+                            calculateFreeformBounds(FINAL_FREEFORM_SCALE)));
             }
         });
         animator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index b1c3791..95ed42a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -30,6 +30,7 @@
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.os.IBinder;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.MotionEvent;
@@ -49,6 +50,9 @@
 import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
 import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
  * {@link DesktopModeWindowDecorViewModel}.
@@ -83,6 +87,9 @@
 
     private TaskCornersListener mCornersListener;
 
+    private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
+    private int mRelayoutBlock;
+
     DesktopModeWindowDecoration(
             Context context,
             DisplayController displayController,
@@ -134,6 +141,13 @@
 
     @Override
     void relayout(ActivityManager.RunningTaskInfo taskInfo) {
+        // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
+        // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
+        // and interferes with the transition animation that is playing at the same time.
+        if (mRelayoutBlock > 0) {
+            return;
+        }
+
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         // Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
         // synced with the buffer transaction (that draws the View). Both will be shown on screen
@@ -455,6 +469,40 @@
         return cornersRegion;
     }
 
+    /**
+     * If transition exists in mTransitionsPausingRelayout, remove the transition and decrement
+     * mRelayoutBlock
+     */
+    void removeTransitionPausingRelayout(IBinder transition) {
+        if (mTransitionsPausingRelayout.remove(transition)) {
+            mRelayoutBlock--;
+        }
+    }
+
+    /**
+     * Add transition to mTransitionsPausingRelayout
+     */
+    void addTransitionPausingRelayout(IBinder transition) {
+        mTransitionsPausingRelayout.add(transition);
+    }
+
+    /**
+     * If two transitions merge and the merged transition is in mTransitionsPausingRelayout,
+     * remove the merged transition from the set and add the transition it was merged into.
+     */
+    public void mergeTransitionPausingRelayout(IBinder merged, IBinder playing) {
+        if (mTransitionsPausingRelayout.remove(merged)) {
+            mTransitionsPausingRelayout.add(playing);
+        }
+    }
+
+    /**
+     * Increase mRelayoutBlock, stopping relayout if mRelayoutBlock is now greater than 0.
+     */
+    public void incrementRelayoutBlock() {
+        mRelayoutBlock++;
+    }
+
     static class Factory {
 
         DesktopModeWindowDecoration create(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index ef8332f..1d416c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -86,9 +86,10 @@
     public void onDragPositioningEnd(float x, float y) {
         PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y,
                 mRepositionStartPoint);
-        if (mHasMoved && DragPositioningCallbackUtility.changeBounds(mCtrlType, mHasMoved,
-                mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
-                mDisplayController, mDesktopWindowDecoration)) {
+        if (mHasMoved) {
+            DragPositioningCallbackUtility.changeBounds(mCtrlType, mHasMoved,
+                    mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
+                    mDisplayController, mDesktopWindowDecoration);
             DragPositioningCallbackUtility.applyTaskBoundsChange(
                     new WindowContainerTransaction(), mDesktopWindowDecoration,
                     mRepositionTaskBounds, mTaskOrganizer);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
new file mode 100644
index 0000000..e06e074
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.app.Instrumentation
+import android.tools.device.flicker.junit.FlickerBuilderProvider
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+
+abstract class BaseBenchmarkTest
+@JvmOverloads
+constructor(
+    protected open val flicker: FlickerTest,
+    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+    protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
+) {
+    /** Specification of the test transition to execute */
+    abstract val transition: FlickerBuilder.() -> Unit
+
+    /**
+     * Entry point for the test runner. It will use this method to initialize and cache flicker
+     * executions
+     */
+    @FlickerBuilderProvider
+    fun buildFlicker(): FlickerBuilder {
+        return FlickerBuilder(instrumentation).apply {
+            setup { flicker.scenario.setIsTablet(tapl.isTablet) }
+            transition()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 86edc25..c98c5a0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -17,24 +17,10 @@
 package com.android.wm.shell.flicker
 
 import android.app.Instrumentation
-import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerBuilderProvider
-import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
-import org.junit.Assume
-import org.junit.Test
 
 /**
  * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR],
@@ -44,124 +30,7 @@
 abstract class BaseTest
 @JvmOverloads
 constructor(
-    protected val flicker: FlickerTest,
-    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
-    protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
-) {
-    /** Specification of the test transition to execute */
-    abstract val transition: FlickerBuilder.() -> Unit
-
-    /**
-     * Entry point for the test runner. It will use this method to initialize and cache flicker
-     * executions
-     */
-    @FlickerBuilderProvider
-    fun buildFlicker(): FlickerBuilder {
-        return FlickerBuilder(instrumentation).apply {
-            setup { flicker.scenario.setIsTablet(tapl.isTablet) }
-            transition()
-        }
-    }
-
-    /** Checks that all parts of the screen are covered during the transition */
-    @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered()
-
-    /**
-     * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
-     */
-    @Presubmit
-    @Test
-    open fun navBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    /**
-     * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
-     * transition
-     */
-    @Presubmit
-    @Test
-    open fun navBarLayerPositionAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerPositionAtStartAndEnd()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
-     *
-     * Note: Phones only
-     */
-    @Presubmit
-    @Test
-    open fun navBarWindowIsAlwaysVisible() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarWindowIsAlwaysVisible()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
-     */
-    @Presubmit
-    @Test
-    open fun taskBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.taskBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
-     *
-     * Note: Large screen only
-     */
-    @Presubmit
-    @Test
-    open fun taskBarWindowIsAlwaysVisible() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.taskBarWindowIsAlwaysVisible()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
-     * transition
-     */
-    @Presubmit
-    @Test
-    open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
-
-    /**
-     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
-     * transition
-     */
-    @Presubmit
-    @Test
-    open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
-
-    /**
-     * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
-     * transition
-     */
-    @Presubmit
-    @Test
-    open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
-
-    /**
-     * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
-     * entries.
-     */
-    @Presubmit
-    @Test
-    open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
-    }
-
-    /**
-     * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
-     * entries.
-     */
-    @Presubmit
-    @Test
-    open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
-    }
-}
+    override val flicker: FlickerTest,
+    instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+    tapl: LauncherInstrumentation = LauncherInstrumentation()
+) : BaseBenchmarkTest(flicker, instrumentation, tapl), ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
new file mode 100644
index 0000000..02d9a056
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.legacy.FlickerTest
+import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
+import org.junit.Assume
+import org.junit.Test
+
+interface ICommonAssertions {
+    val flicker: FlickerTest
+
+    /** Checks that all parts of the screen are covered during the transition */
+    @Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered()
+
+    /**
+     * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
+     */
+    @Presubmit
+    @Test
+    fun navBarLayerIsVisibleAtStartAndEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    /**
+     * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
+     * transition
+     */
+    @Presubmit
+    @Test
+    fun navBarLayerPositionAtStartAndEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerPositionAtStartAndEnd()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
+     *
+     * Note: Phones only
+     */
+    @Presubmit
+    @Test
+    fun navBarWindowIsAlwaysVisible() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsAlwaysVisible()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
+     */
+    @Presubmit
+    @Test
+    fun taskBarLayerIsVisibleAtStartAndEnd() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
+     *
+     * Note: Large screen only
+     */
+    @Presubmit
+    @Test
+    fun taskBarWindowIsAlwaysVisible() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarWindowIsAlwaysVisible()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
+     * transition
+     */
+    @Presubmit
+    @Test
+    fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /**
+     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+     * transition
+     */
+    @Presubmit
+    @Test
+    fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
+
+    /**
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
+     * transition
+     */
+    @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
+
+    /**
+     * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
+     * entries.
+     */
+    @Presubmit
+    @Test
+    fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+    }
+
+    /**
+     * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
+     * entries.
+     */
+    @Presubmit
+    @Test
+    fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index d53eac0..b7e73ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -26,6 +26,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -34,6 +35,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,29 +51,19 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CopyContentInSplit(flicker: FlickerTest) : SplitScreenBase(flicker) {
-    private val textEditApp = SplitScreenUtils.getIme(instrumentation)
-    private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
-    private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
-
+class CopyContentInSplit(override val flicker: FlickerTest) :
+    CopyContentInSplitBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
-            transitions {
-                SplitScreenUtils.copyContentInSplit(
-                    instrumentation,
-                    device,
-                    primaryApp,
-                    textEditApp
-                )
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() {
+    override fun cujCompleted() {
         flicker.appWindowIsVisibleAtStart(primaryApp)
         flicker.appWindowIsVisibleAtStart(textEditApp)
         flicker.splitScreenDividerIsVisibleAtStart()
@@ -128,8 +120,8 @@
                         ComponentNameMatcher.SNAPSHOT,
                         ComponentNameMatcher.IME_SNAPSHOT,
                         EdgeExtensionComponentMatcher(),
-                        MagnifierLayer,
-                        PopupWindowLayer
+                        magnifierLayer,
+                        popupWindowLayer
                     )
             )
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 1b55f39..3fd6d17 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -17,22 +17,21 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -48,37 +47,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
+class DismissSplitScreenByDivider(override val flicker: FlickerTest) :
+    DismissSplitScreenByDividerBenchmark(flicker), ICommonAssertions {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
-            transitions {
-                if (tapl.isTablet) {
-                    SplitScreenUtils.dragDividerToDismissSplit(
-                        device,
-                        wmHelper,
-                        dragToRight = false,
-                        dragToBottom = true
-                    )
-                } else {
-                    SplitScreenUtils.dragDividerToDismissSplit(
-                        device,
-                        wmHelper,
-                        dragToRight = true,
-                        dragToBottom = true
-                    )
-                }
-                wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible()
@@ -176,12 +154,4 @@
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 2e81b30..e05b221 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -17,18 +17,18 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,21 +44,14 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByGoHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class DismissSplitScreenByGoHome(override val flicker: FlickerTest) :
+    DismissSplitScreenByGoHomeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
-            transitions {
-                tapl.goHome()
-                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 5180791..0b0a3da 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -24,6 +24,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -32,8 +33,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,24 +49,19 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class DragDividerToResize(override val flicker: FlickerTest) :
+    DragDividerToResizeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
-            transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @Before
-    fun before() {
-        Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
-    }
-
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() {
+    override fun cujCompleted() {
         flicker.appWindowIsVisibleAtStart(primaryApp)
         flicker.appWindowIsVisibleAtStart(secondaryApp)
         flicker.splitScreenDividerIsVisibleAtStart()
@@ -74,9 +69,6 @@
         flicker.appWindowIsVisibleAtEnd(primaryApp)
         flicker.appWindowIsVisibleAtEnd(secondaryApp)
         flicker.splitScreenDividerIsVisibleAtEnd()
-
-        // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
-        // robust enough to get the correct end state.
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 69da1e2..e558686 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -26,6 +25,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -34,9 +34,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -53,40 +51,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
-    @Before
-    fun before() {
-        Assume.assumeTrue(tapl.isTablet)
-    }
-
+class EnterSplitScreenByDragFromAllApps(override val flicker: FlickerTest) :
+    EnterSplitScreenByDragFromAllAppsBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                tapl.goHome()
-                primaryApp.launchViaIntent(wmHelper)
-            }
-            transitions {
-                tapl.launchedAppState.taskbar
-                    .openAllApps()
-                    .getAppIcon(secondaryApp.appName)
-                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     @FlakyTest(bugId = 245472831)
     @Test
     fun splitScreenDividerBecomesVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 1773846..ab8ecc5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -26,6 +25,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerBecomesVisible
@@ -33,9 +33,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -52,39 +50,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
-    private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
-
-    @Before
-    fun before() {
-        Assume.assumeTrue(tapl.isTablet)
-    }
-
+class EnterSplitScreenByDragFromNotification(override val flicker: FlickerTest) :
+    EnterSplitScreenByDragFromNotificationBenchmark(flicker), ICommonAssertions {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                // Send a notification
-                sendNotificationApp.launchViaIntent(wmHelper)
-                sendNotificationApp.postNotification(wmHelper)
-                tapl.goHome()
-                primaryApp.launchViaIntent(wmHelper)
-            }
-            transitions {
-                SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
-            }
-            teardown { sendNotificationApp.exit(wmHelper) }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
-
     @FlakyTest(bugId = 245472831)
     @Test
     fun splitScreenDividerBecomesVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index c1977e9..516ca97 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,15 +24,14 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,42 +47,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromShortcut(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
-    @Before
-    fun before() {
-        Assume.assumeTrue(tapl.isTablet)
-    }
+class EnterSplitScreenByDragFromShortcut(override val flicker: FlickerTest) :
+    EnterSplitScreenByDragFromShortcutBenchmark(flicker), ICommonAssertions {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                tapl.goHome()
-                SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
-                primaryApp.launchViaIntent(wmHelper)
-            }
-            transitions {
-                tapl.launchedAppState.taskbar
-                    .getAppIcon(secondaryApp.appName)
-                    .openDeepShortcutMenu()
-                    .getMenuItem("Split Screen Secondary Activity")
-                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 3bea66e..4af7e24 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -26,6 +25,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -34,9 +34,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -53,41 +51,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
-    @Before
-    fun before() {
-        Assume.assumeTrue(tapl.isTablet)
-    }
-
+class EnterSplitScreenByDragFromTaskbar(override val flicker: FlickerTest) :
+    EnterSplitScreenByDragFromTaskbarBenchmark(flicker), ICommonAssertions {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                tapl.goHome()
-                SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
-                primaryApp.launchViaIntent(wmHelper)
-            }
-            transitions {
-                tapl.launchedAppState.taskbar
-                    .getAppIcon(secondaryApp.appName)
-                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     @FlakyTest(bugId = 245472831)
     @Test
     fun splitScreenDividerBecomesVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index c453877..faad9e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -17,20 +17,20 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,31 +46,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenFromOverview(flicker: FlickerTest) : SplitScreenBase(flicker) {
+class EnterSplitScreenFromOverview(override val flicker: FlickerTest) :
+    EnterSplitScreenFromOverviewBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                primaryApp.launchViaIntent(wmHelper)
-                secondaryApp.launchViaIntent(wmHelper)
-                tapl.goHome()
-                wmHelper
-                    .StateSyncBuilder()
-                    .withAppTransitionIdle()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
-            }
-            transitions {
-                SplitScreenUtils.splitFromOverview(tapl, device)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index 7abdc06..195b73a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -20,25 +20,33 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.BaseTest
+import com.android.wm.shell.flicker.BaseBenchmarkTest
 
-abstract class SplitScreenBase(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class SplitScreenBase(flicker: FlickerTest) : BaseBenchmarkTest(flicker) {
     protected val context: Context = instrumentation.context
     protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            setup {
-                tapl.setEnableRotation(true)
-                setRotation(flicker.scenario.startRotation)
-                tapl.setExpectedRotation(flicker.scenario.startRotation.value)
-                tapl.workspace.switchToOverview().dismissAllTasks()
-            }
-            teardown {
-                primaryApp.exit(wmHelper)
-                secondaryApp.exit(wmHelper)
-            }
+    protected open val defaultSetup: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setEnableRotation(true)
+            setRotation(flicker.scenario.startRotation)
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+            tapl.workspace.switchToOverview().dismissAllTasks()
         }
+    }
+
+    protected open val defaultTeardown: FlickerBuilder.() -> Unit = {
+        teardown {
+            primaryApp.exit(wmHelper)
+            secondaryApp.exit(wmHelper)
+        }
+    }
+
+    protected open val withoutTracing: FlickerBuilder.() -> Unit = {
+        withoutLayerTracing()
+        withoutWindowManagerTracing()
+        withoutTransitionTracing()
+        withoutTransactionsTracing()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index fbb7c71..8cf871f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -20,14 +20,12 @@
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
-import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.helpers.WindowUtils
-import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -36,6 +34,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,98 +50,19 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchAppByDoubleTapDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) :
+    SwitchAppByDoubleTapDividerBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
-            transitions {
-                SplitScreenUtils.doubleTapDividerToSwitch(device)
-                wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
-
-                waitForLayersToSwitch(wmHelper)
-                waitForWindowsToSwitch(wmHelper)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
-        wmHelper
-            .StateSyncBuilder()
-            .add("appWindowsSwitched") {
-                val primaryAppWindow =
-                    it.wmState.visibleWindows.firstOrNull { window ->
-                        primaryApp.windowMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-                val secondaryAppWindow =
-                    it.wmState.visibleWindows.firstOrNull { window ->
-                        secondaryApp.windowMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-
-                if (isLandscape(flicker.scenario.endRotation)) {
-                    return@add if (flicker.scenario.isTablet) {
-                        secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
-                    } else {
-                        primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
-                    }
-                } else {
-                    return@add if (flicker.scenario.isTablet) {
-                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
-                    } else {
-                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
-                    }
-                }
-            }
-            .waitForAndVerify()
-    }
-
-    private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
-        wmHelper
-            .StateSyncBuilder()
-            .add("appLayersSwitched") {
-                val primaryAppLayer =
-                    it.layerState.visibleLayers.firstOrNull { window ->
-                        primaryApp.layerMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-                val secondaryAppLayer =
-                    it.layerState.visibleLayers.firstOrNull { window ->
-                        secondaryApp.layerMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-
-                val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
-                val secondaryVisibleRegion =
-                    secondaryAppLayer.visibleRegion?.bounds ?: return@add false
-
-                if (isLandscape(flicker.scenario.endRotation)) {
-                    return@add if (flicker.scenario.isTablet) {
-                        secondaryVisibleRegion.right <= primaryVisibleRegion.left
-                    } else {
-                        primaryVisibleRegion.right <= secondaryVisibleRegion.left
-                    }
-                } else {
-                    return@add if (flicker.scenario.isTablet) {
-                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
-                    } else {
-                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
-                    }
-                }
-            }
-            .waitForAndVerify()
-    }
-
-    private fun isLandscape(rotation: Rotation): Boolean {
-        val displayBounds = WindowUtils.getDisplayBounds(rotation)
-        return displayBounds.width > displayBounds.height
-    }
-
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() {
+    override fun cujCompleted() {
         flicker.appWindowIsVisibleAtStart(primaryApp)
         flicker.appWindowIsVisibleAtStart(secondaryApp)
         flicker.splitScreenDividerIsVisibleAtStart()
@@ -150,9 +70,6 @@
         flicker.appWindowIsVisibleAtEnd(primaryApp)
         flicker.appWindowIsVisibleAtEnd(secondaryApp)
         flicker.splitScreenDividerIsVisibleAtEnd()
-
-        // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
-        // robust enough to get the correct end state.
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index d675bfb..078d95d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,11 +24,12 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,29 +45,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromAnotherApp(flicker: FlickerTest) : SplitScreenBase(flicker) {
-    val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
-
+class SwitchBackToSplitFromAnotherApp(override val flicker: FlickerTest) :
+    SwitchBackToSplitFromAnotherAppBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-
-                thirdApp.launchViaIntent(wmHelper)
-                wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
-            }
-            transitions {
-                tapl.launchedAppState.quickSwitchToPreviousApp()
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 9f4cb8c..7c84243 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,11 +24,12 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,28 +45,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class SwitchBackToSplitFromHome(override val flicker: FlickerTest) :
+    SwitchBackToSplitFromHomeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-
-                tapl.goHome()
-                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-            }
-            transitions {
-                tapl.workspace.quickSwitchToPreviousApp()
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index a33d8ca..7c46d3e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,11 +24,12 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,28 +45,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromRecent(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class SwitchBackToSplitFromRecent(override val flicker: FlickerTest) :
+    SwitchBackToSplitFromRecentBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-
-                tapl.goHome()
-                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-            }
-            transitions {
-                tapl.workspace.switchToOverview().currentTask.open()
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 4c96b3a..3b2da8d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -24,6 +24,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.appWindowBecomesVisible
@@ -36,6 +37,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,32 +53,19 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBetweenSplitPairs(flicker: FlickerTest) : SplitScreenBase(flicker) {
-    private val thirdApp = SplitScreenUtils.getIme(instrumentation)
-    private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
-
+class SwitchBetweenSplitPairs(override val flicker: FlickerTest) :
+    SwitchBetweenSplitPairsBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
-            }
-            transitions {
-                tapl.launchedAppState.quickSwitchToPreviousApp()
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
-            teardown {
-                thirdApp.exit(wmHelper)
-                fourthApp.exit(wmHelper)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() {
+    override fun cujCompleted() {
         flicker.appWindowIsVisibleAtStart(thirdApp)
         flicker.appWindowIsVisibleAtStart(fourthApp)
         flicker.splitScreenDividerIsVisibleAtStart()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
new file mode 100644
index 0000000..a189a3f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val textEditApp = SplitScreenUtils.getIme(instrumentation)
+    protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
+    protected val popupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
+            transitions {
+                SplitScreenUtils.copyContentInSplit(
+                    instrumentation,
+                    device,
+                    primaryApp,
+                    textEditApp
+                )
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    open fun cujCompleted() {
+        // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
new file mode 100644
index 0000000..55ab7b3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            transitions {
+                if (tapl.isTablet) {
+                    SplitScreenUtils.dragDividerToDismissSplit(
+                        device,
+                        wmHelper,
+                        dragToRight = false,
+                        dragToBottom = true
+                    )
+                } else {
+                    SplitScreenUtils.dragDividerToDismissSplit(
+                        device,
+                        wmHelper,
+                        dragToRight = true,
+                        dragToBottom = true
+                    )
+                }
+                wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
new file mode 100644
index 0000000..c4cfd1a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            transitions {
+                tapl.goHome()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
new file mode 100644
index 0000000..146287c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
+    }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    open fun cujCompleted() {
+        // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
+        // robust enough to get the correct end state.
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
new file mode 100644
index 0000000..cc71502
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                tapl.goHome()
+                primaryApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                tapl.launchedAppState.taskbar
+                    .openAllApps()
+                    .getAppIcon(secondaryApp.appName)
+                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() =
+        flicker.splitScreenEntered(
+            primaryApp,
+            secondaryApp,
+            fromOtherApp = false,
+            appExistAtStart = false
+        )
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
new file mode 100644
index 0000000..de78f09
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                // Send a notification
+                sendNotificationApp.launchViaIntent(wmHelper)
+                sendNotificationApp.postNotification(wmHelper)
+                tapl.goHome()
+                primaryApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
+            }
+            teardown { sendNotificationApp.exit(wmHelper) }
+        }
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() =
+        flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
new file mode 100644
index 0000000..a29eb40
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    protected val thisTransition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.goHome()
+            SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+            primaryApp.launchViaIntent(wmHelper)
+        }
+        transitions {
+            tapl.launchedAppState.taskbar
+                .getAppIcon(secondaryApp.appName)
+                .openDeepShortcutMenu()
+                .getMenuItem("Split Screen Secondary Activity")
+                .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+            SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+        }
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() =
+        flicker.splitScreenEntered(
+            primaryApp,
+            secondaryApp,
+            fromOtherApp = false,
+            appExistAtStart = false
+        )
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
new file mode 100644
index 0000000..b2395ca
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                tapl.goHome()
+                SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+                primaryApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                tapl.launchedAppState.taskbar
+                    .getAppIcon(secondaryApp.appName)
+                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() =
+        flicker.splitScreenEntered(
+            primaryApp,
+            secondaryApp,
+            fromOtherApp = false,
+            appExistAtStart = false
+        )
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
new file mode 100644
index 0000000..e1d85d0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                primaryApp.launchViaIntent(wmHelper)
+                secondaryApp.launchViaIntent(wmHelper)
+                tapl.goHome()
+                wmHelper
+                    .StateSyncBuilder()
+                    .withAppTransitionIdle()
+                    .withHomeActivityVisible()
+                    .waitForAndVerify()
+            }
+            transitions {
+                SplitScreenUtils.splitFromOverview(tapl, device)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
new file mode 100644
index 0000000..ba8c460
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            transitions {
+                SplitScreenUtils.doubleTapDividerToSwitch(device)
+                wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+                waitForLayersToSwitch(wmHelper)
+                waitForWindowsToSwitch(wmHelper)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("appWindowsSwitched") {
+                val primaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        primaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        secondaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+
+                if (isLandscape(flicker.scenario.endRotation)) {
+                    return@add if (flicker.scenario.isTablet) {
+                        secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+                    } else {
+                        primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+                    }
+                } else {
+                    return@add if (flicker.scenario.isTablet) {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    } else {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    }
+                }
+            }
+            .waitForAndVerify()
+    }
+
+    private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("appLayersSwitched") {
+                val primaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        primaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        secondaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+
+                val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+                val secondaryVisibleRegion =
+                    secondaryAppLayer.visibleRegion?.bounds ?: return@add false
+
+                if (isLandscape(flicker.scenario.endRotation)) {
+                    return@add if (flicker.scenario.isTablet) {
+                        secondaryVisibleRegion.right <= primaryVisibleRegion.left
+                    } else {
+                        primaryVisibleRegion.right <= secondaryVisibleRegion.left
+                    }
+                } else {
+                    return@add if (flicker.scenario.isTablet) {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    } else {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    }
+                }
+            }
+            .waitForAndVerify()
+    }
+
+    private fun isLandscape(rotation: Rotation): Boolean {
+        val displayBounds = WindowUtils.getDisplayBounds(rotation)
+        return displayBounds.width > displayBounds.height
+    }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    open fun cujCompleted() {
+        // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
+        // robust enough to get the correct end state.
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
new file mode 100644
index 0000000..bbb2edc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
+
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+                thirdApp.launchViaIntent(wmHelper)
+                wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
+            }
+            transitions {
+                tapl.launchedAppState.quickSwitchToPreviousApp()
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
new file mode 100644
index 0000000..fa38293
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+                tapl.goHome()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+            }
+            transitions {
+                tapl.workspace.quickSwitchToPreviousApp()
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
new file mode 100644
index 0000000..1064bd9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+                tapl.goHome()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+            }
+            transitions {
+                tapl.workspace.switchToOverview().currentTask.open()
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
new file mode 100644
index 0000000..8f4393f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thirdApp = SplitScreenUtils.getIme(instrumentation)
+    protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
+            }
+            transitions {
+                tapl.launchedAppState.quickSwitchToPreviousApp()
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+            teardown {
+                thirdApp.exit(wmHelper)
+                fourthApp.exit(wmHelper)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui") @Presubmit @Test open fun cujCompleted() {}
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
index 1347e06..60ee918 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
@@ -112,9 +112,9 @@
     @Test
     public void testOnTaskProfileLocked() {
         ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class);
-        mImpl.onTaskProfileLocked(info);
-        verify(mCallback).onTaskProfileLocked(eq(info));
-        verify(mOtherCallback).onTaskProfileLocked(eq(info));
+        mImpl.onTaskProfileLocked(info, 0);
+        verify(mCallback).onTaskProfileLocked(eq(info), eq(0));
+        verify(mOtherCallback).onTaskProfileLocked(eq(info), eq(0));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 63de74f..d6387ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_NONE;
@@ -82,6 +83,8 @@
 @RunWith(AndroidTestingRunner.class)
 public class DesktopModeControllerTest extends ShellTestCase {
 
+    private static final int SECOND_DISPLAY = 2;
+
     @Mock
     private ShellController mShellController;
     @Mock
@@ -248,22 +251,22 @@
     public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
         // Set up two active tasks on desktop, task2 is on top of task1.
         RunningTaskInfo freeformTask1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask1.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
         mDesktopModeTaskRepository.updateVisibleFreeformTasks(
-                freeformTask1.taskId, false /* visible */);
+                DEFAULT_DISPLAY, freeformTask1.taskId, false /* visible */);
         RunningTaskInfo freeformTask2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask2.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
         mDesktopModeTaskRepository.updateVisibleFreeformTasks(
-                freeformTask2.taskId, false /* visible */);
+                DEFAULT_DISPLAY, freeformTask2.taskId, false /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
                 freeformTask1);
         when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
                 freeformTask2);
 
         // Run show desktop apps logic
-        mController.showDesktopApps();
+        mController.showDesktopApps(DEFAULT_DISPLAY);
 
         final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
         // Check wct has reorder calls
@@ -283,17 +286,19 @@
     @Test
     public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() {
         final RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
+                true /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
         final RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
+                true /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
 
-        mController.showDesktopApps();
+        mController.showDesktopApps(DEFAULT_DISPLAY);
 
         final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
         // Check wct has reorder calls
@@ -312,17 +317,19 @@
     @Test
     public void testShowDesktopApps_someAppsInvisible_reordersAll() {
         final RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
+                false /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
         final RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
+                true /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
 
-        mController.showDesktopApps();
+        mController.showDesktopApps(DEFAULT_DISPLAY);
 
         final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
         // Both tasks should be reordered to top, even if one was already visible.
@@ -336,38 +343,87 @@
     }
 
     @Test
+    public void testShowDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() {
+        RunningTaskInfo taskDefaultDisplay = createFreeformTask(DEFAULT_DISPLAY);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+                DEFAULT_DISPLAY, taskDefaultDisplay.taskId, false /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(taskDefaultDisplay.taskId)).thenReturn(
+                taskDefaultDisplay);
+
+        RunningTaskInfo taskSecondDisplay = createFreeformTask(SECOND_DISPLAY);
+        mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+                SECOND_DISPLAY, taskSecondDisplay.taskId, false /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(taskSecondDisplay.taskId)).thenReturn(
+                taskSecondDisplay);
+
+        mController.showDesktopApps(DEFAULT_DISPLAY);
+
+        WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+        assertThat(wct.getHierarchyOps()).hasSize(1);
+        HierarchyOp op = wct.getHierarchyOps().get(0);
+        assertThat(op.getContainer()).isEqualTo(taskDefaultDisplay.token.asBinder());
+    }
+
+    @Test
     public void testGetVisibleTaskCount_noTasks_returnsZero() {
-        assertThat(mController.getVisibleTaskCount()).isEqualTo(0);
+        assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0);
     }
 
     @Test
     public void testGetVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
         RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
+                true /* visible */);
 
         RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
+                true /* visible */);
 
-        assertThat(mController.getVisibleTaskCount()).isEqualTo(2);
+        assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2);
     }
 
     @Test
     public void testGetVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
         RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
+                true /* visible */);
 
         RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, false /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
+                false /* visible */);
 
-        assertThat(mController.getVisibleTaskCount()).isEqualTo(1);
+        assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1);
+    }
+
+    @Test
+    public void testGetVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
+        RunningTaskInfo taskDefaultDisplay = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY,
+                taskDefaultDisplay.taskId,
+                true /* visible */);
+
+        RunningTaskInfo taskSecondDisplay = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(SECOND_DISPLAY,
+                taskSecondDisplay.taskId,
+                true /* visible */);
+
+        assertThat(mController.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 45cb3a0..3bc2f0e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -17,10 +17,12 @@
 package com.android.wm.shell.desktopmode
 
 import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestShellExecutor
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.fail
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,8 +43,8 @@
         val listener = TestListener()
         repo.addActiveTaskListener(listener)
 
-        repo.addActiveTask(1)
-        assertThat(listener.activeTaskChangedCalls).isEqualTo(1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1)
         assertThat(repo.isActiveTask(1)).isTrue()
     }
 
@@ -51,9 +53,9 @@
         val listener = TestListener()
         repo.addActiveTaskListener(listener)
 
-        repo.addActiveTask(1)
-        repo.addActiveTask(1)
-        assertThat(listener.activeTaskChangedCalls).isEqualTo(1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1)
     }
 
     @Test
@@ -61,9 +63,22 @@
         val listener = TestListener()
         repo.addActiveTaskListener(listener)
 
-        repo.addActiveTask(1)
-        repo.addActiveTask(2)
-        assertThat(listener.activeTaskChangedCalls).isEqualTo(2)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2)
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
+    }
+
+    @Test
+    fun addActiveTask_multipleDisplays_notifiesCorrectListener() {
+        val listener = TestListener()
+        repo.addActiveTaskListener(listener)
+
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2)
+        repo.addActiveTask(SECOND_DISPLAY, taskId = 3)
+
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
+        assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(1)
     }
 
     @Test
@@ -71,10 +86,10 @@
         val listener = TestListener()
         repo.addActiveTaskListener(listener)
 
-        repo.addActiveTask(1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
         repo.removeActiveTask(1)
         // Notify once for add and once for remove
-        assertThat(listener.activeTaskChangedCalls).isEqualTo(2)
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
         assertThat(repo.isActiveTask(1)).isFalse()
     }
 
@@ -83,7 +98,17 @@
         val listener = TestListener()
         repo.addActiveTaskListener(listener)
         repo.removeActiveTask(99)
-        assertThat(listener.activeTaskChangedCalls).isEqualTo(0)
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(0)
+    }
+
+    @Test
+    fun remoteActiveTask_listenerForOtherDisplayNotNotified() {
+        val listener = TestListener()
+        repo.addActiveTaskListener(listener)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        repo.removeActiveTask(1)
+        assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(0)
+        assertThat(repo.isActiveTask(1)).isFalse()
     }
 
     @Test
@@ -93,14 +118,27 @@
 
     @Test
     fun addListener_notifiesVisibleFreeformTask() {
-        repo.updateVisibleFreeformTasks(1, true)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
         val listener = TestVisibilityListener()
         val executor = TestShellExecutor()
         repo.addVisibleTasksListener(listener, executor)
         executor.flushAll()
 
-        assertThat(listener.hasVisibleFreeformTasks).isTrue()
-        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(1)
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+    }
+
+    @Test
+    fun addListener_tasksOnDifferentDisplay_doesNotNotify() {
+        repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        executor.flushAll()
+
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+        // One call as adding listener notifies it
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(0)
     }
 
     @Test
@@ -108,13 +146,61 @@
         val listener = TestVisibilityListener()
         val executor = TestShellExecutor()
         repo.addVisibleTasksListener(listener, executor)
-        repo.updateVisibleFreeformTasks(1, true)
-        repo.updateVisibleFreeformTasks(2, true)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
         executor.flushAll()
 
-        assertThat(listener.hasVisibleFreeformTasks).isTrue()
-        // Equal to 2 because adding the listener notifies the current state
-        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2)
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+    }
+
+    @Test
+    fun updateVisibleFreeformTasks_addVisibleTaskNotifiesListenerForThatDisplay() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        executor.flushAll()
+
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+        assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isFalse()
+        assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0)
+
+        repo.updateVisibleFreeformTasks(displayId = 1, taskId = 2, visible = true)
+        executor.flushAll()
+
+        // Listener for secondary display is notified
+        assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
+        assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
+        // No changes to listener for default display
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+    }
+
+    @Test
+    fun updateVisibleFreeformTasks_taskOnDefaultBecomesVisibleOnSecondDisplay_listenersNotified() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        executor.flushAll()
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+
+        // Mark task 1 visible on secondary display
+        repo.updateVisibleFreeformTasks(displayId = 1, taskId = 1, visible = true)
+        executor.flushAll()
+
+        // Default display should have 2 calls
+        // 1 - visible task added
+        // 2 - visible task removed
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+
+        // Secondary display should have 1 call for visible task added
+        assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
+        assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
     }
 
     @Test
@@ -122,52 +208,83 @@
         val listener = TestVisibilityListener()
         val executor = TestShellExecutor()
         repo.addVisibleTasksListener(listener, executor)
-        repo.updateVisibleFreeformTasks(1, true)
-        repo.updateVisibleFreeformTasks(2, true)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
         executor.flushAll()
 
-        assertThat(listener.hasVisibleFreeformTasks).isTrue()
-        repo.updateVisibleFreeformTasks(1, false)
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
         executor.flushAll()
 
-        // Equal to 2 because adding the listener notifies the current state
-        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2)
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
 
-        repo.updateVisibleFreeformTasks(2, false)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
         executor.flushAll()
 
-        assertThat(listener.hasVisibleFreeformTasks).isFalse()
-        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
     }
 
     @Test
     fun getVisibleTaskCount() {
         // No tasks, count is 0
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
 
         // New task increments count to 1
-        repo.updateVisibleFreeformTasks(taskId = 1, visible = true)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
 
         // Visibility update to same task does not increase count
-        repo.updateVisibleFreeformTasks(taskId = 1, visible = true)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
 
         // Second task visible increments count
-        repo.updateVisibleFreeformTasks(taskId = 2, visible = true)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(2)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
 
         // Hiding a task decrements count
-        repo.updateVisibleFreeformTasks(taskId = 1, visible = false)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
 
         // Hiding all tasks leaves count at 0
-        repo.updateVisibleFreeformTasks(taskId = 2, visible = false)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
+        assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0)
 
         // Hiding a not existing task, count remains at 0
-        repo.updateVisibleFreeformTasks(taskId = 999, visible = false)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 999, visible = false)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+    }
+
+    @Test
+    fun getVisibleTaskCount_multipleDisplays() {
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+
+        // New task on default display increments count for that display only
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+
+        // New task on secondary display, increments count for that display only
+        repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 2, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+
+        // Marking task visible on another display, updates counts for both displays
+        repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+
+        // Marking task that is on secondary display, hidden on default display, does not affect
+        // secondary display
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+
+        // Hiding a task on that display, decrements count
+        repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = false)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
     }
 
     @Test
@@ -197,19 +314,40 @@
     }
 
     class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
-        var activeTaskChangedCalls = 0
-        override fun onActiveTasksChanged() {
-            activeTaskChangedCalls++
+        var activeChangesOnDefaultDisplay = 0
+        var activeChangesOnSecondaryDisplay = 0
+        override fun onActiveTasksChanged(displayId: Int) {
+            when (displayId) {
+                DEFAULT_DISPLAY -> activeChangesOnDefaultDisplay++
+                SECOND_DISPLAY -> activeChangesOnSecondaryDisplay++
+                else -> fail("Active task listener received unexpected display id: $displayId")
+            }
         }
     }
 
     class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
-        var hasVisibleFreeformTasks = false
-        var visibleFreeformTaskChangedCalls = 0
+        var hasVisibleTasksOnDefaultDisplay = false
+        var hasVisibleTasksOnSecondaryDisplay = false
 
-        override fun onVisibilityChanged(hasVisibleTasks: Boolean) {
-            hasVisibleFreeformTasks = hasVisibleTasks
-            visibleFreeformTaskChangedCalls++
+        var visibleChangesOnDefaultDisplay = 0
+        var visibleChangesOnSecondaryDisplay = 0
+
+        override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+            when (displayId) {
+                DEFAULT_DISPLAY -> {
+                    hasVisibleTasksOnDefaultDisplay = hasVisibleFreeformTasks
+                    visibleChangesOnDefaultDisplay++
+                }
+                SECOND_DISPLAY -> {
+                    hasVisibleTasksOnSecondaryDisplay = hasVisibleFreeformTasks
+                    visibleChangesOnSecondaryDisplay++
+                }
+                else -> fail("Visible task listener received unexpected display id: $displayId")
+            }
         }
     }
+
+    companion object {
+        const val SECOND_DISPLAY = 1
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index c9bd695..f506969 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -25,6 +25,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.os.Binder
 import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
 import android.view.WindowManager
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_NONE
@@ -84,10 +85,10 @@
     @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
     @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
 
-    lateinit var mockitoSession: StaticMockitoSession
-    lateinit var controller: DesktopTasksController
-    lateinit var shellInit: ShellInit
-    lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
+    private lateinit var mockitoSession: StaticMockitoSession
+    private lateinit var controller: DesktopTasksController
+    private lateinit var shellInit: ShellInit
+    private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
 
     // Mock running tasks are registered here so we can get the list from mock shell task organizer
     private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -155,7 +156,7 @@
         markTaskHidden(task1)
         markTaskHidden(task2)
 
-        controller.showDesktopApps()
+        controller.showDesktopApps(DEFAULT_DISPLAY)
 
         val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(3)
@@ -173,7 +174,7 @@
         markTaskVisible(task1)
         markTaskVisible(task2)
 
-        controller.showDesktopApps()
+        controller.showDesktopApps(DEFAULT_DISPLAY)
 
         val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(3)
@@ -191,7 +192,7 @@
         markTaskHidden(task1)
         markTaskVisible(task2)
 
-        controller.showDesktopApps()
+        controller.showDesktopApps(DEFAULT_DISPLAY)
 
         val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(3)
@@ -205,7 +206,7 @@
     fun showDesktopApps_noActiveTasks_reorderHomeToTop() {
         val homeTask = setUpHomeTask()
 
-        controller.showDesktopApps()
+        controller.showDesktopApps(DEFAULT_DISPLAY)
 
         val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(1)
@@ -213,8 +214,26 @@
     }
 
     @Test
+    fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() {
+        val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
+        val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
+        setUpHomeTask(SECOND_DISPLAY)
+        val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
+        markTaskHidden(taskDefaultDisplay)
+        markTaskHidden(taskSecondDisplay)
+
+        controller.showDesktopApps(DEFAULT_DISPLAY)
+
+        val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+        assertThat(wct.hierarchyOps).hasSize(2)
+        // Expect order to be from bottom: home, task
+        wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
+        wct.assertReorderAt(index = 1, taskDefaultDisplay)
+    }
+
+    @Test
     fun getVisibleTaskCount_noTasks_returnsZero() {
-        assertThat(controller.getVisibleTaskCount()).isEqualTo(0)
+        assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
     }
 
     @Test
@@ -222,7 +241,7 @@
         setUpHomeTask()
         setUpFreeformTask().also(::markTaskVisible)
         setUpFreeformTask().also(::markTaskVisible)
-        assertThat(controller.getVisibleTaskCount()).isEqualTo(2)
+        assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
     }
 
     @Test
@@ -230,7 +249,15 @@
         setUpHomeTask()
         setUpFreeformTask().also(::markTaskVisible)
         setUpFreeformTask().also(::markTaskHidden)
-        assertThat(controller.getVisibleTaskCount()).isEqualTo(1)
+        assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+    }
+
+    @Test
+    fun getVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
+        setUpHomeTask()
+        setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
+        setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
+        assertThat(controller.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
     }
 
     @Test
@@ -258,6 +285,7 @@
         controller.moveToDesktop(fullscreenTask)
 
         with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+            // Operations should include home task, freeform task
             assertThat(hierarchyOps).hasSize(3)
             assertReorderSequence(homeTask, freeformTask, fullscreenTask)
             assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
@@ -266,6 +294,28 @@
     }
 
     @Test
+    fun moveToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() {
+        setUpHomeTask(displayId = DEFAULT_DISPLAY)
+        val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+        val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+        markTaskHidden(freeformTaskDefault)
+
+        val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY)
+        val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
+        markTaskHidden(freeformTaskSecond)
+
+        controller.moveToDesktop(fullscreenTaskDefault)
+
+        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+            // Check that hierarchy operations do not include tasks from second display
+            assertThat(hierarchyOps.map { it.container })
+                .doesNotContain(homeTaskSecond.token.asBinder())
+            assertThat(hierarchyOps.map { it.container })
+                .doesNotContain(freeformTaskSecond.token.asBinder())
+        }
+    }
+
+    @Test
     fun moveToFullscreen() {
         val task = setUpFreeformTask()
         controller.moveToFullscreen(task)
@@ -281,6 +331,19 @@
     }
 
     @Test
+    fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() {
+        val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+        val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
+
+        controller.moveToFullscreen(taskDefaultDisplay)
+
+        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+            assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
+            assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder())
+        }
+    }
+
+    @Test
     fun getTaskWindowingMode() {
         val fullscreenTask = setUpFullscreenTask()
         val freeformTask = setUpFreeformTask()
@@ -324,6 +387,18 @@
     }
 
     @Test
+    fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY)
+        createFreeformTask(displayId = SECOND_DISPLAY)
+
+        val result =
+            controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay))
+        assertThat(result).isNull()
+    }
+
+    @Test
     fun handleRequest_freeformTask_freeformVisible_returnNull() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
@@ -362,6 +437,18 @@
     }
 
     @Test
+    fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_returnSwitchToFullscreenWCT() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
+        createFreeformTask(displayId = SECOND_DISPLAY)
+
+        val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
+        assertThat(result?.changes?.get(taskDefaultDisplay.token.asBinder())?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+    }
+
+    @Test
     fun handleRequest_notOpenOrToFrontTransition_returnNull() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
@@ -400,35 +487,43 @@
         assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
     }
 
-    private fun setUpFreeformTask(): RunningTaskInfo {
-        val task = createFreeformTask()
+    private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+        val task = createFreeformTask(displayId)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
-        desktopModeTaskRepository.addActiveTask(task.taskId)
+        desktopModeTaskRepository.addActiveTask(displayId, task.taskId)
         desktopModeTaskRepository.addOrMoveFreeformTaskToTop(task.taskId)
         runningTasks.add(task)
         return task
     }
 
-    private fun setUpHomeTask(): RunningTaskInfo {
-        val task = createHomeTask()
+    private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+        val task = createHomeTask(displayId)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
         runningTasks.add(task)
         return task
     }
 
-    private fun setUpFullscreenTask(): RunningTaskInfo {
-        val task = createFullscreenTask()
+    private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+        val task = createFullscreenTask(displayId)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
         runningTasks.add(task)
         return task
     }
 
     private fun markTaskVisible(task: RunningTaskInfo) {
-        desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = true)
+        desktopModeTaskRepository.updateVisibleFreeformTasks(
+            task.displayId,
+            task.taskId,
+            visible = true
+        )
     }
 
     private fun markTaskHidden(task: RunningTaskInfo) {
-        desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = false)
+        desktopModeTaskRepository.updateVisibleFreeformTasks(
+            task.displayId,
+            task.taskId,
+            visible = false
+        )
     }
 
     private fun getLatestWct(
@@ -457,6 +552,10 @@
     ): TransitionRequestInfo {
         return TransitionRequestInfo(type, task, null /* remoteTransition */)
     }
+
+    companion object {
+        const val SECOND_DISPLAY = 2
+    }
 }
 
 private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index dc91d75..cf1ff32 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -21,14 +21,17 @@
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.view.Display.DEFAULT_DISPLAY
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 
 class DesktopTestHelpers {
     companion object {
         /** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */
         @JvmStatic
-        fun createFreeformTask(): RunningTaskInfo {
+        @JvmOverloads
+        fun createFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
             return TestRunningTaskInfoBuilder()
+                    .setDisplayId(displayId)
                     .setToken(MockToken().token())
                     .setActivityType(ACTIVITY_TYPE_STANDARD)
                     .setWindowingMode(WINDOWING_MODE_FREEFORM)
@@ -38,8 +41,10 @@
 
         /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */
         @JvmStatic
-        fun createFullscreenTask(): RunningTaskInfo {
+        @JvmOverloads
+        fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
             return TestRunningTaskInfoBuilder()
+                    .setDisplayId(displayId)
                     .setToken(MockToken().token())
                     .setActivityType(ACTIVITY_TYPE_STANDARD)
                     .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
@@ -49,8 +54,10 @@
 
         /** Create a new home task */
         @JvmStatic
-        fun createHomeTask(): RunningTaskInfo {
+        @JvmOverloads
+        fun createHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
             return TestRunningTaskInfoBuilder()
+                    .setDisplayId(displayId)
                     .setToken(MockToken().token())
                     .setActivityType(ACTIVITY_TYPE_HOME)
                     .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 6995d10..04f2c99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -268,7 +268,7 @@
     }
 
     @Test
-    public void saveReentryState_userHasResized_savesSize() {
+    public void saveReentryState_nonEmptyUserResizeBounds_savesSize() {
         final Rect bounds = new Rect(0, 0, 10, 10);
         final Rect resizedBounds = new Rect(0, 0, 30, 30);
         when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
@@ -281,6 +281,19 @@
     }
 
     @Test
+    public void saveReentryState_emptyUserResizeBounds_savesSize() {
+        final Rect bounds = new Rect(0, 0, 10, 10);
+        final Rect resizedBounds = new Rect(0, 0, 0, 0);
+        when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
+        when(mMockPipTouchHandler.getUserResizeBounds()).thenReturn(resizedBounds);
+        when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(true);
+
+        mPipController.saveReentryState(bounds);
+
+        verify(mMockPipBoundsState).saveReentryState(new Size(10, 10), 1.0f);
+    }
+
+    @Test
     public void onDisplayConfigurationChanged_inPip_movePip() {
         final int displayId = 1;
         final Rect bounds = new Rect(0, 0, 10, 10);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
new file mode 100644
index 0000000..348b365
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.graphics.PointF
+import android.graphics.Rect
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.window.WindowContainerToken
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [DragPositioningCallbackUtility].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DragPositioningCallbackUtilityTest
+ */
+@RunWith(AndroidTestingRunner::class)
+class DragPositioningCallbackUtilityTest {
+    @Mock
+    private lateinit var mockWindowDecoration: WindowDecoration<*>
+    @Mock
+    private lateinit var taskToken: WindowContainerToken
+    @Mock
+    private lateinit var taskBinder: IBinder
+    @Mock
+    private lateinit var mockDisplayController: DisplayController
+    @Mock
+    private lateinit var mockDisplayLayout: DisplayLayout
+    @Mock
+    private lateinit var mockDisplay: Display
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(taskToken.asBinder()).thenReturn(taskBinder)
+        whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+        whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+        whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+            (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+        }
+
+        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
+        }
+        mockWindowDecoration.mDisplay = mockDisplay
+        whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+    }
+
+    @Test
+    fun testChangeBoundsDoesNotChangeHeightWhenLessThanMin() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect()
+
+        // 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
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+            false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration)
+
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 5)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    fun testChangeBoundsDoesNotChangeWidthWhenLessThanMin() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect()
+
+        // 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
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+            false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration)
+
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 5)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    fun testChangeBoundsDoesNotChangeHeightWhenNegative() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect()
+
+        // Resize to width of 95px and width of -5px with minimum of 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 5
+        val newY = STARTING_BOUNDS.top.toFloat() + 105
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+            false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration)
+
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 5)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    fun testChangeBoundsRunsWhenResizeBoundsValid() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect()
+
+        // 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
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+                false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+                mockDisplayController, mockWindowDecoration)
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 80)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 80)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    fun testChangeBoundsDoesNotRunWithNegativeHeightAndWidth() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect()
+        // Shrink to height -5px and width -5px with both min height/width equal to 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 105
+        val newY = STARTING_BOUNDS.top.toFloat() + 105
+
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+            false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration)
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
+        var hasMoved = false
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
+            STARTING_BOUNDS.bottom.toFloat())
+        val repositionTaskBounds = Rect()
+        // Initial resize to width and height 110px.
+        var newX = STARTING_BOUNDS.right.toFloat() + 10
+        var newY = STARTING_BOUNDS.bottom.toFloat() + 10
+        var delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+        assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+            hasMoved, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration))
+        hasMoved = true
+        // Resize width to 120px, height to disallowed area which should not result in a change.
+        newX += 10
+        newY = DISALLOWED_RESIZE_AREA.top.toFloat()
+        delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+        assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+            hasMoved, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration))
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right + 20)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom + 10)
+    }
+
+    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 const val NAVBAR_HEIGHT = 50
+        private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+        private val DISALLOWED_RESIZE_AREA = Rect(
+            DISPLAY_BOUNDS.left,
+            DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+            DISPLAY_BOUNDS.right,
+            DISPLAY_BOUNDS.bottom)
+        private val STABLE_BOUNDS = Rect(
+            DISPLAY_BOUNDS.left,
+            DISPLAY_BOUNDS.top,
+            DISPLAY_BOUNDS.right,
+            DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+        )
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 84ccdde..5bea8f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -14,7 +14,6 @@
 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.DragPositioningCallback.CTRL_TYPE_BOTTOM
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
@@ -22,7 +21,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.Mockito.any
 import org.mockito.Mockito.argThat
 import org.mockito.Mockito.never
@@ -72,10 +71,10 @@
                 mockDragStartListener
             )
 
-        `when`(taskToken.asBinder()).thenReturn(taskBinder)
-        `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
-        `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
-        `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+        whenever(taskToken.asBinder()).thenReturn(taskBinder)
+        whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+        whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+        whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
             (i.arguments.first() as Rect).set(STABLE_BOUNDS)
         }
 
@@ -89,7 +88,7 @@
             configuration.windowConfiguration.bounds = STARTING_BOUNDS
         }
         mockWindowDecoration.mDisplay = mockDisplay
-        `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+        whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
     }
 
     @Test
@@ -237,293 +236,6 @@
         })
     }
 
-    @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)
-            }
-        })
-    }
-
-    fun testDragResize_toDisallowedBounds_freezesAtLimit() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner
-                STARTING_BOUNDS.right.toFloat(),
-                STARTING_BOUNDS.bottom.toFloat()
-        )
-
-        // Resize the task by 10px to the right and bottom, a valid destination
-        val newBounds = Rect(
-                STARTING_BOUNDS.left,
-                STARTING_BOUNDS.top,
-                STARTING_BOUNDS.right + 10,
-                STARTING_BOUNDS.bottom + 10)
-        taskPositioner.onDragPositioningMove(
-                newBounds.right.toFloat(),
-                newBounds.bottom.toFloat()
-        )
-
-        // Resize the task by another 10px to the right (allowed) and to just in the disallowed
-        // area of the Y coordinate.
-        val newBounds2 = Rect(
-                newBounds.left,
-                newBounds.top,
-                newBounds.right + 10,
-                DISALLOWED_RESIZE_AREA.top
-        )
-        taskPositioner.onDragPositioningMove(
-                newBounds2.right.toFloat(),
-                newBounds2.bottom.toFloat()
-        )
-
-        taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat())
-
-        // The first resize falls in the allowed area, verify there's a change for it.
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder && change.ofBounds(newBounds)
-            }
-        })
-        // The second resize falls in the disallowed area, verify there's no change for it.
-        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder && change.ofBounds(newBounds2)
-            }
-        })
-        // Instead, there should be a change for its allowed portion (the X movement) with the Y
-        // staying frozen in the last valid resize position.
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder && change.ofBounds(
-                        Rect(
-                                newBounds2.left,
-                                newBounds2.top,
-                                newBounds2.right,
-                                newBounds.bottom // Stayed at the first resize destination.
-                        )
-                )
-            }
-        })
-    }
-
     private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
         return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
                 bounds == configuration.windowConfiguration.bounds
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index bf365ca..498082b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -34,7 +34,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.Mockito.any
 import org.mockito.Mockito.argThat
 import org.mockito.Mockito.never
@@ -85,10 +85,10 @@
                 mockDragStartListener
             )
 
-        `when`(taskToken.asBinder()).thenReturn(taskBinder)
-        `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
-        `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
-        `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+        whenever(taskToken.asBinder()).thenReturn(taskBinder)
+        whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+        whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+        whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
             (i.arguments.first() as Rect).set(STABLE_BOUNDS)
         }
 
@@ -102,7 +102,7 @@
             configuration.windowConfiguration.bounds = STARTING_BOUNDS
         }
         mockDesktopWindowDecoration.mDisplay = mockDisplay
-        `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+        whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
     }
 
     @Test
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index bfe4eaf..613f52b 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -38,7 +38,7 @@
 
 using namespace renderthread;
 
-static float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
+float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
     // We should always have a known destination colorspace. If we don't we must be in some
     // legacy mode where we're lost and also definitely not going to HDR
     if (destColorspace == nullptr) {
diff --git a/libs/hwui/effects/GainmapRenderer.h b/libs/hwui/effects/GainmapRenderer.h
index 4ed2445..0ab03f0 100644
--- a/libs/hwui/effects/GainmapRenderer.h
+++ b/libs/hwui/effects/GainmapRenderer.h
@@ -25,6 +25,8 @@
 
 namespace android::uirenderer {
 
+float getTargetHdrSdrRatio(const SkColorSpace* destColorspace);
+
 void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
                        const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
                        SkCanvas::SrcRectConstraint constraint,
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index a4960ea..c58ba68 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -26,6 +26,7 @@
 #include "SkM44.h"
 #include "include/gpu/GpuTypes.h" // from Skia
 #include "utils/GLUtils.h"
+#include <effects/GainmapRenderer.h>
 
 namespace android {
 namespace uirenderer {
@@ -129,6 +130,7 @@
     info.height = fboSize.height();
     mat4.getColMajor(&info.transform[0]);
     info.color_space_ptr = canvas->imageInfo().colorSpace();
+    info.currentHdrSdrRatio = getTargetHdrSdrRatio(info.color_space_ptr);
 
     // ensure that the framebuffer that the webview will render into is bound before we clear
     // the stencil and/or draw the functor.
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index e6ef95b..e299d12 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -30,6 +30,7 @@
 #include "renderthread/VulkanManager.h"
 #include "thread/ThreadBase.h"
 #include "utils/TimeUtils.h"
+#include "effects/GainmapRenderer.h"
 
 namespace android {
 namespace uirenderer {
@@ -73,6 +74,7 @@
             .clip_right = mClip.fRight,
             .clip_bottom = mClip.fBottom,
             .is_layer = !vulkan_info.fFromSwapchainOrAndroidWindow,
+            .currentHdrSdrRatio = getTargetHdrSdrRatio(mImageInfo.colorSpace()),
     };
     mat4.getColMajor(&params.transform[0]);
     params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer;
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index e168a7b..adf3c06 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -32,6 +32,7 @@
 #include "renderthread/EglManager.h"
 #include "thread/ThreadBase.h"
 #include "utils/TimeUtils.h"
+#include "effects/GainmapRenderer.h"
 
 #include <SkBlendMode.h>
 
@@ -139,6 +140,7 @@
         info.height = mFBInfo.height();
         mat4.getColMajor(&info.transform[0]);
         info.color_space_ptr = canvas->imageInfo().colorSpace();
+        info.currentHdrSdrRatio = getTargetHdrSdrRatio(info.color_space_ptr);
 
         glViewport(0, 0, info.width, info.height);
 
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index 501b8df..7888c87 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -86,6 +86,11 @@
         // commands are issued.
         kStatusDrew = 0x4
     };
+
+    // The current HDR/SDR ratio that we are rendering to. The transform to SDR will already
+    // be baked into the color_space_ptr, so this is just to indicate the amount of extended
+    // range is available if desired
+    float currentHdrSdrRatio;
 };  // struct DrawGlInfo
 
 }  // namespace uirenderer
diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h
index 5c59657..8f7063d 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -71,6 +71,11 @@
 
   // Input: Whether destination surface is offscreen surface.
   bool is_layer;
+
+  // The current HDR/SDR ratio that we are rendering to. The transform to SDR will already
+  // be baked into the color_space_ptr, so this is just to indicate the amount of extended
+  // range is available if desired
+  float currentHdrSdrRatio;
 };
 
 }  // namespace uirenderer
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index 6bfcd82..8c35da1 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -58,6 +58,7 @@
             android:textSize="14sp"
             android:layout_marginTop="2dp"
             style="@style/TextAppearance"
+            android:focusable="true"
             android:textColor="?android:attr/textColorSecondary"/>
 
     </LinearLayout>
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 74072e9..2502bbf 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -113,17 +113,11 @@
     <!-- Back button for the helper consent dialog [CHAR LIMIT=30] -->
     <string name="consent_back">Back</string>
 
-    <!-- Action when permission list view is expanded CHAR LIMIT=30] -->
-    <string name="permission_expanded">Expanded</string>
+    <!-- Expand permission in the list CHAR LIMIT=30] -->
+    <string name="permission_expand">Expand <xliff:g id="permission_type" example="Notification">%1$s</xliff:g></string>
 
-    <!-- Expand action permission list CHAR LIMIT=30] -->
-    <string name="permission_expand">Expand</string>
-
-    <!-- Action when permission list view is collapsed CHAR LIMIT=30] -->
-    <string name="permission_collapsed">Collapsed</string>
-
-    <!-- Collapse action permission list CHAR LIMIT=30] -->
-    <string name="permission_collapse">Collapse</string>
+    <!-- Collapse permission int the list CHAR LIMIT=30] -->
+    <string name="permission_collapse">Collapse <xliff:g id="permission_type" example="Notification">%1$s</xliff:g></string>
 
     <!-- ================== System data transfer ==================== -->
     <!-- Title of the permission sync confirmation dialog. [CHAR LIMIT=NONE] -->
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index b86ef64..f594bf2 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -124,7 +124,7 @@
         }
 
         setAccessibility(view, viewType,
-                AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand);
+                AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand, 0);
 
         // Add expand buttons if the permissions are more than PERMISSION_SIZE in this list also
         // make the summary invisible by default.
@@ -137,18 +137,16 @@
                     viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_less);
                     viewHolder.mPermissionSummary.setVisibility(View.VISIBLE);
                     viewHolder.mExpandButton.setTag(R.drawable.btn_expand_less);
-                    view.setContentDescription(mContext.getString(R.string.permission_expanded));
                     setAccessibility(view, viewType,
-                            AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_collapse);
-                    viewHolder.mPermissionSummary.setFocusable(true);
+                            AccessibilityNodeInfo.ACTION_CLICK,
+                            R.string.permission_collapse, R.string.permission_expand);
                 } else {
                     viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_more);
                     viewHolder.mPermissionSummary.setVisibility(View.GONE);
                     viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
-                    view.setContentDescription(mContext.getString(R.string.permission_collapsed));
                     setAccessibility(view, viewType,
-                            AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expanded);
-                    viewHolder.mPermissionSummary.setFocusable(false);
+                            AccessibilityNodeInfo.ACTION_CLICK,
+                            R.string.permission_expand, R.string.permission_collapse);
                 }
             });
         } else {
@@ -200,14 +198,20 @@
         }
     }
 
-    private void setAccessibility(View view, int viewType, int action, int resourceId) {
-        final String actionString = mContext.getString(resourceId);
+    private void setAccessibility(View view, int viewType, int action, int statusResourceId,
+            int actionResourceId) {
         final String permission = mContext.getString(sTitleMap.get(viewType));
+
+        if (actionResourceId != 0) {
+            view.announceForAccessibility(
+                    getHtmlFromResources(mContext, actionResourceId, permission));
+        }
+
         view.setAccessibilityDelegate(new View.AccessibilityDelegate() {
             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                 super.onInitializeAccessibilityNodeInfo(host, info);
                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(action,
-                        actionString + permission));
+                        getHtmlFromResources(mContext, statusResourceId, permission)));
             }
         });
     }
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index a3b2752..3402857 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -86,6 +86,8 @@
   <string name="use_provider_for_all_description">This password manager for <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g> will store your passwords and passkeys to help you easily sign in</string>
   <!-- This is a label for a button that sets this password manager as the default. [CHAR LIMIT=20] -->
   <string name="set_as_default">Set as default</string>
+  <!-- This is a button text to navigate the user to their system settings. [CHAR LIMIT=30] -->
+  <string name="settings">Settings</string>
   <!-- This is a label for a button that makes this password manager be used just in this specific case. [CHAR LIMIT=20] -->
   <string name="use_once">Use once</string>
   <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are passwords and passkeys. [CHAR LIMIT=80] -->
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 01f92c4..19b7e85 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -17,6 +17,7 @@
 package com.android.settingslib.collapsingtoolbar;
 
 import android.app.ActionBar;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -59,7 +60,8 @@
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        if (mCustomizeLayoutResId > 0 && !BuildCompatUtils.isAtLeastS()) {
+        // for backward compatibility on R devices or wearable devices due to small device size.
+        if (mCustomizeLayoutResId > 0 && (!BuildCompatUtils.isAtLeastS() || isWatch())) {
             super.setContentView(mCustomizeLayoutResId);
             return;
         }
@@ -157,6 +159,14 @@
         return getToolbarDelegate().getAppBarLayout();
     }
 
+    private boolean isWatch() {
+        PackageManager packageManager = getPackageManager();
+        if (packageManager == null) {
+            return false;
+        }
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+
     private CollapsingToolbarDelegate getToolbarDelegate() {
         if (mToolbardelegate == null) {
             mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
index 3c45112..dae48db 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
@@ -17,6 +17,7 @@
 package com.android.settingslib.applications;
 
 import android.app.AppGlobals;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -94,6 +95,10 @@
 
     }
 
+    public @Nullable String getSummary() {
+        return this.summary;
+    }
+
     @Override
     public Drawable loadIcon() {
         final IconDrawableFactory factory = IconDrawableFactory.newInstance(mContext);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index e4cc9f1..6a5535d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -100,6 +100,5 @@
         Settings.System.CAMERA_FLASH_NOTIFICATION,
         Settings.System.SCREEN_FLASH_NOTIFICATION,
         Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
-        Settings.System.SMOOTH_DISPLAY
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 4b72063..85623b2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -226,6 +226,5 @@
         VALIDATORS.put(System.CAMERA_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION_COLOR, ANY_INTEGER_VALIDATOR);
-        VALIDATORS.put(System.SMOOTH_DISPLAY, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index d1bd5e6..46b45d1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -34,7 +34,6 @@
 
 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.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE;
 import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
 import static com.android.providers.settings.SettingsState.getTypeFromKey;
 import static com.android.providers.settings.SettingsState.getUserIdFromKey;
@@ -5674,7 +5673,7 @@
                             providers.addAll(Arrays.asList(resources.getStringArray(resourceId)));
                         } catch (Resources.NotFoundException e) {
                             Slog.w(LOG_TAG,
-                                    "Get default array Cred Provider not found: " + e.toString());
+                                "Get default array Cred Provider not found: " + e.toString());
                         }
                         try {
                             final String storedValue = resources.getString(resourceId);
@@ -5683,7 +5682,7 @@
                             }
                         } catch (Resources.NotFoundException e) {
                             Slog.w(LOG_TAG,
-                                    "Get default Cred Provider not found: " + e.toString());
+                                "Get default Cred Provider not found: " + e.toString());
                         }
 
                         if (!providers.isEmpty()) {
@@ -5732,8 +5731,8 @@
                     final Setting currentSetting = secureSettings
                             .getSettingLocked(Settings.Secure.CREDENTIAL_SERVICE);
                     if (currentSetting.isNull()) {
-                        final int resourceId = com.android.internal.R.array
-                                .config_defaultCredentialProviderService;
+                        final int resourceId =
+                            com.android.internal.R.array.config_defaultCredentialProviderService;
                         final Resources resources = getContext().getResources();
                         // If the config has not be defined we might get an exception.
                         final List<String> providers = new ArrayList<>();
@@ -5741,7 +5740,7 @@
                             providers.addAll(Arrays.asList(resources.getStringArray(resourceId)));
                         } catch (Resources.NotFoundException e) {
                             Slog.w(LOG_TAG,
-                                    "Get default array Cred Provider not found: " + e.toString());
+                                "Get default array Cred Provider not found: " + e.toString());
                         }
 
                         if (!providers.isEmpty()) {
@@ -5840,44 +5839,12 @@
                     currentVersion = 218;
                 }
 
-                // v218: Convert Smooth Display and Force Peak Refresh Rate to a boolean
                 if (currentVersion == 218) {
-                    final String peakRefreshRateSettingName = "peak_refresh_rate";
-                    final String minRefreshRateSettingName = "min_refresh_rate";
-
-                    final SettingsState systemSettings = getSystemSettingsLocked(userId);
-                    final Setting peakRefreshRateSetting =
-                            systemSettings.getSettingLocked(peakRefreshRateSettingName);
-                    final Setting minRefreshRateSetting =
-                            systemSettings.getSettingLocked(minRefreshRateSettingName);
-
-                    float peakRefreshRate = DEFAULT_REFRESH_RATE;
-                    float minRefreshRate = 0;
-                    try {
-                        if (!peakRefreshRateSetting.isNull()) {
-                            peakRefreshRate = Float.parseFloat(peakRefreshRateSetting.getValue());
-                        }
-                    } catch (NumberFormatException e) {
-                        // Do nothing. Overwrite with default value.
-                    }
-                    try {
-                        if (!minRefreshRateSetting.isNull()) {
-                            minRefreshRate = Float.parseFloat(minRefreshRateSetting.getValue());
-                        }
-                    } catch (NumberFormatException e) {
-                        // Do nothing. Overwrite with default value.
-                    }
-
-                    systemSettings.deleteSettingLocked(peakRefreshRateSettingName);
-                    systemSettings.deleteSettingLocked(minRefreshRateSettingName);
-
-                    systemSettings.insertSettingLocked(Settings.System.SMOOTH_DISPLAY,
-                            peakRefreshRate > DEFAULT_REFRESH_RATE ? "1" : "0", /* tag= */ null,
-                            /* makeDefault= */ false, SettingsState.SYSTEM_PACKAGE_NAME);
-                    systemSettings.insertSettingLocked(Settings.System.FORCE_PEAK_REFRESH_RATE,
-                            minRefreshRate > 0 ? "1" : "0", /* tag= */ null,
-                            /* makeDefault= */ false, SettingsState.SYSTEM_PACKAGE_NAME);
-
+                    // Version 219: Removed
+                    // TODO(b/211737588): Back up the Smooth Display setting
+                    // Future upgrades to the `peak_refresh_rate` and `min_refresh_rate` settings
+                    // should account for the database in a non-upgraded and upgraded (change id:
+                    // Ib2cb2dd100f06f5452083b7606109a486e795a0e) state.
                     currentVersion = 219;
                 }
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 36aa2ac..706666c 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -97,7 +97,8 @@
                     Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
                     Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
                     Settings.System.DESKTOP_MODE, // developer setting for internal prototyping
-                    Settings.System.FORCE_PEAK_REFRESH_RATE, // depends on hardware capabilities
+                    Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
+                    Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.SCREEN_BRIGHTNESS_FLOAT,
                     Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
                     Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6e55000..c8eb4b4 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -992,6 +992,18 @@
             android:excludeFromRecents="true"
             android:resizeableActivity="false"
             android:theme="@android:style/Theme.NoDisplay" />
+
+        <activity
+            android:name=".notetask.LaunchNotesRoleSettingsTrampolineActivity"
+            android:exported="true"
+            android:excludeFromRecents="true"
+            android:resizeableActivity="false"
+            android:theme="@android:style/Theme.NoDisplay" >
+            <intent-filter>
+                <action android:name="com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <!-- endregion -->
 
         <!-- started from ControlsRequestReceiver -->
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 77ddc6e..1ce3472 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -55,6 +55,7 @@
 madym@google.com
 mankoff@google.com
 mateuszc@google.com
+mgalhardo@google.com
 michaelmikhil@google.com
 michschn@google.com
 mkephart@google.com
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 436145e..3244eb4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -131,7 +131,8 @@
     /**
      * A rounded corner clipping that makes QS feel as if it were behind everything.
      */
-    void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible);
+    void setFancyClipping(int leftInset, int top, int rightInset, int bottom, int cornerRadius,
+            boolean visible, boolean fullWidth);
 
     /**
      * @return if quick settings is fully collapsed currently
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index 29832a0..934fa6f 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -30,7 +30,7 @@
 
         <FrameLayout
             android:id="@+id/inout_container"
-            android:layout_height="17dp"
+            android:layout_height="@*android:dimen/status_bar_system_icon_intrinsic_size"
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical">
             <ImageView
@@ -39,24 +39,25 @@
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_down"
                 android:visibility="gone"
-                android:paddingEnd="2dp"
+                android:paddingEnd="2sp"
                 />
             <ImageView
                 android:id="@+id/mobile_out"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_up"
-                android:paddingEnd="2dp"
+                android:paddingEnd="2sp"
                 android:visibility="gone"
                 />
         </FrameLayout>
         <ImageView
             android:id="@+id/mobile_type"
-            android:layout_height="wrap_content"
+            android:layout_height="@dimen/status_bar_mobile_signal_size"
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical"
-            android:paddingStart="2.5dp"
-            android:paddingEnd="1dp"
+            android:adjustViewBounds="true"
+            android:paddingStart="2.5sp"
+            android:paddingEnd="1sp"
             android:visibility="gone" />
         <Space
             android:id="@+id/mobile_roaming_space"
@@ -70,14 +71,14 @@
             android:layout_gravity="center_vertical">
             <com.android.systemui.statusbar.AnimatedImageView
                 android:id="@+id/mobile_signal"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
+                android:layout_height="@dimen/status_bar_mobile_signal_size"
+                android:layout_width="@dimen/status_bar_mobile_signal_size"
                 systemui:hasOverlappingRendering="false"
                 />
             <ImageView
                 android:id="@+id/mobile_roaming"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_width="@dimen/status_bar_mobile_signal_size"
+                android:layout_height="@dimen/status_bar_mobile_signal_size"
                 android:src="@drawable/stat_sys_roaming"
                 android:contentDescription="@string/data_connection_roaming"
                 android:visibility="gone" />
diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
index ee8d4883..a35504f 100644
--- a/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
@@ -16,16 +16,12 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"
-    android:viewportHeight="24"
-    android:viewportWidth="24">
+    android:viewportWidth="24"
+    android:viewportHeight="24">
     <path
-        android:fillAlpha="1"
-        android:fillColor="@android:color/white"
-        android:fillType="nonZero"
-        android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
+        android:pathData="M23.41,6L22,4.59C21.63,4.21 21.12,4 20.59,4C20.06,4 19.55,4.21 19.18,4.59L11.39,12.38L9.09,14.68L8.04,18.9C8.01,18.96 8,19.04 8,19.11C8,19.6 8.4,20 8.89,20C8.96,20 9.04,19.99 9.11,19.97L13.33,18.92L15.63,16.62L23.42,8.83C23.79,8.45 24,7.94 24,7.41C24,6.88 23.79,6.37 23.41,6ZM14.21,15.21L13.21,16.21L11.8,14.8L12.8,13.8L20.59,6L22,7.41L14.21,15.21Z"
+        android:fillColor="@android:color/white"/>
     <path
-        android:fillAlpha="1"
-        android:fillColor="@android:color/white"
-        android:fillType="nonZero"
-        android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
+        android:pathData="M6.688,20C2.047,20 0.333,18.65 0.333,16C0.333,13.61 2.439,12.474 5.713,12C6.792,11.844 7.344,11.397 7.344,10.927C7.344,9.625 4.679,9.705 3.833,9.667V7.667C3.833,7.667 6.792,7.667 8.208,8.729C8.932,9.272 9.333,9.979 9.333,11.05C9.333,12.52 8.281,13.677 5.713,13.885C4.017,14.023 2.333,14.52 2.333,16C2.333,17.33 4.013,18 7.333,18L6.688,20Z"
+        android:fillColor="@android:color/white"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
index 7590182..860fc7d 100644
--- a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
@@ -13,19 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportHeight="24"
-    android:viewportWidth="24">
-    <path
-        android:fillAlpha="1"
-        android:fillColor="#636C6F"
-        android:fillType="nonZero"
-        android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
-    <path
-        android:fillAlpha="1"
-        android:fillColor="#636C6F"
-        android:fillType="nonZero"
-        android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
-</vector>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_note_task_shortcut_widget_background" />
+    <foreground android:drawable="@drawable/ic_note_task_shortcut_widget_foreground" />
+</adaptive-icon>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_background.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_background.xml
new file mode 100644
index 0000000..9f98f07
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_background.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <path
+      android:pathData="M0,0h108v108h-108z"
+      android:fillColor="#0B57D0"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_foreground.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_foreground.xml
new file mode 100644
index 0000000..fcb3ef4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_foreground.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <path
+      android:pathData="M74.92,43L72.33,40.42C71.65,39.72 70.72,39.33 69.75,39.33C68.78,39.33 67.84,39.72 67.16,40.42L52.88,54.7L48.67,58.91L46.74,66.65C46.69,66.76 46.67,66.91 46.67,67.04C46.67,67.93 47.4,68.67 48.3,68.67C48.43,68.67 48.57,68.65 48.7,68.61L56.44,66.69L60.65,62.47L74.94,48.19C75.61,47.49 76,46.56 76,45.58C76,44.61 75.61,43.68 74.92,43ZM58.05,59.88L56.22,61.72L53.63,59.13L55.47,57.3L69.75,43L72.33,45.58L58.05,59.88Z"
+      android:fillColor="#ffffff"/>
+  <path
+      android:pathData="M44.26,68.67C35.75,68.67 32.61,66.19 32.61,61.33C32.61,56.95 36.47,54.87 42.47,54C44.45,53.71 45.46,52.89 45.46,52.03C45.46,49.65 40.58,49.79 39.03,49.72V46.06C39.03,46.06 44.45,46.06 47.05,48C48.37,49 49.11,50.3 49.11,52.26C49.11,54.95 47.18,57.07 42.47,57.46C39.36,57.71 36.28,58.62 36.28,61.33C36.28,63.77 39.36,65 45.44,65L44.26,68.67Z"
+      android:fillColor="#ffffff"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 441f963..e989372 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -126,8 +126,7 @@
     <com.android.systemui.battery.BatteryMeterView
         android:id="@+id/batteryRemainingIcon"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/large_screen_shade_header_min_height"
-        app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+        android:layout_height="0dp"
         app:layout_constrainedWidth="true"
         app:textAppearance="@style/TextAppearance.QS.Status"
         app:layout_constraintStart_toEndOf="@id/statusIcons"
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
index 0ea0653..473ab08 100644
--- a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
+++ b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
@@ -24,11 +24,11 @@
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:gravity="center_vertical"
-        android:layout_marginStart="2.5dp"
+        android:layout_marginStart="2.5sp"
     >
         <FrameLayout
                 android:id="@+id/inout_container"
-                android:layout_height="17dp"
+                android:layout_height="@*android:dimen/status_bar_system_icon_intrinsic_size"
                 android:layout_width="wrap_content"
                 android:gravity="center_vertical" >
             <ImageView
@@ -37,14 +37,14 @@
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_down"
                 android:visibility="gone"
-                android:paddingEnd="2dp"
+                android:paddingEnd="2sp"
             />
             <ImageView
                 android:id="@+id/wifi_out"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_up"
-                android:paddingEnd="2dp"
+                android:paddingEnd="2sp"
                 android:visibility="gone"
             />
         </FrameLayout>
@@ -62,7 +62,7 @@
         <View
             android:id="@+id/wifi_signal_spacer"
             android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
-            android:layout_height="4dp"
+            android:layout_height="4sp"
             android:visibility="gone" />
 
         <!-- Looks like CarStatusBar uses this... -->
@@ -75,7 +75,7 @@
         <View
             android:id="@+id/wifi_airplane_spacer"
             android:layout_width="@dimen/status_bar_airplane_spacer_width"
-            android:layout_height="4dp"
+            android:layout_height="4sp"
             android:visibility="gone"
         />
     </com.android.keyguard.AlphaOptimizedLinearLayout>
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-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 2086459..f40615e 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -17,7 +17,7 @@
 -->
 <resources>
     <!-- gap on either side of status bar notification icons -->
-    <dimen name="status_bar_icon_padding">1dp</dimen>
+    <dimen name="status_bar_icon_padding">1sp</dimen>
 
     <dimen name="controls_header_horizontal_padding">28dp</dimen>
     <dimen name="controls_content_margin_horizontal">40dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0aa880f..f5c4a4e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -122,26 +122,26 @@
     <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
 
     <!-- Default horizontal drawable padding for status bar icons. -->
-    <dimen name="status_bar_horizontal_padding">2.5dp</dimen>
+    <dimen name="status_bar_horizontal_padding">2.5sp</dimen>
 
     <!-- Height of the battery icon in the status bar. -->
-    <dimen name="status_bar_battery_icon_height">13.0dp</dimen>
+    <dimen name="status_bar_battery_icon_height">13.0sp</dimen>
 
     <!-- Width of the battery icon in the status bar. The battery drawable assumes a 12x20 canvas,
-    so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
-    <dimen name="status_bar_battery_icon_width">7.8dp</dimen>
+    so the width of the icon should be 13.0sp * (12.0 / 20.0) -->
+    <dimen name="status_bar_battery_icon_width">7.8sp</dimen>
 
-    <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
+    <!-- The battery icon is 13sp tall, but the other system icons are 15sp tall (see
          @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
-         the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
+         the drawables themselves. So, the battery icon may need an extra 1sp of spacing so that its
          bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
-    <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
+    <dimen name="status_bar_battery_extra_vertical_spacing">1sp</dimen>
 
     <!-- The font size for the clock in the status bar. -->
     <dimen name="status_bar_clock_size">14sp</dimen>
 
     <!-- The starting padding for the clock in the status bar. -->
-    <dimen name="status_bar_clock_starting_padding">7dp</dimen>
+    <dimen name="status_bar_clock_starting_padding">7sp</dimen>
 
     <!-- The end padding for the clock in the status bar. -->
     <dimen name="status_bar_clock_end_padding">0dp</dimen>
@@ -153,16 +153,19 @@
     <dimen name="status_bar_left_clock_end_padding">2dp</dimen>
 
     <!-- Spacing after the wifi signals that is present if there are any icons following it. -->
-    <dimen name="status_bar_wifi_signal_spacer_width">2.5dp</dimen>
+    <dimen name="status_bar_wifi_signal_spacer_width">2.5sp</dimen>
 
     <!-- Size of the view displaying the wifi signal icon in the status bar. -->
-    <dimen name="status_bar_wifi_signal_size">@*android:dimen/status_bar_system_icon_size</dimen>
+    <dimen name="status_bar_wifi_signal_size">13sp</dimen>
+
+    <!-- Size of the view displaying the mobile signal icon in the status bar. -->
+    <dimen name="status_bar_mobile_signal_size">13sp</dimen>
 
     <!-- Spacing before the airplane mode icon if there are any icons preceding it. -->
-    <dimen name="status_bar_airplane_spacer_width">4dp</dimen>
+    <dimen name="status_bar_airplane_spacer_width">4sp</dimen>
 
     <!-- Spacing between system icons. -->
-    <dimen name="status_bar_system_icon_spacing">0dp</dimen>
+    <dimen name="status_bar_system_icon_spacing">2sp</dimen>
 
     <!-- The amount to scale each of the status bar icons by. A value of 1 means no scaling. -->
     <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item>
@@ -310,7 +313,7 @@
     <dimen name="snooze_snackbar_min_height">56dp</dimen>
 
     <!-- size at which Notification icons will be drawn in the status bar -->
-    <dimen name="status_bar_icon_drawing_size">15dp</dimen>
+    <dimen name="status_bar_icon_drawing_size">15sp</dimen>
 
     <!-- size at which Notification icons will be drawn on Ambient Display -->
     <dimen name="status_bar_icon_drawing_size_dark">
@@ -321,22 +324,22 @@
     <item type="dimen" name="status_bar_icon_drawing_alpha">90%</item>
 
     <!-- gap on either side of status bar notification icons -->
-    <dimen name="status_bar_icon_padding">0dp</dimen>
+    <dimen name="status_bar_icon_padding">0sp</dimen>
 
     <!-- the padding on the start of the statusbar -->
-    <dimen name="status_bar_padding_start">8dp</dimen>
+    <dimen name="status_bar_padding_start">8sp</dimen>
 
     <!-- the padding on the end of the statusbar -->
-    <dimen name="status_bar_padding_end">8dp</dimen>
+    <dimen name="status_bar_padding_end">8sp</dimen>
 
     <!-- the padding on the top of the statusbar (usually 0) -->
-    <dimen name="status_bar_padding_top">0dp</dimen>
+    <dimen name="status_bar_padding_top">0sp</dimen>
 
     <!-- the radius of the overflow dot in the status bar -->
-    <dimen name="overflow_dot_radius">2dp</dimen>
+    <dimen name="overflow_dot_radius">2sp</dimen>
 
     <!-- the padding between dots in the icon overflow -->
-    <dimen name="overflow_icon_dot_padding">3dp</dimen>
+    <dimen name="overflow_icon_dot_padding">3sp</dimen>
 
     <!-- Dimensions related to screenshots -->
 
@@ -617,8 +620,8 @@
     <dimen name="qs_footer_icon_size">20dp</dimen>
     <dimen name="qs_header_row_min_height">48dp</dimen>
 
-    <dimen name="qs_header_non_clickable_element_height">24dp</dimen>
-    <dimen name="new_qs_header_non_clickable_element_height">24dp</dimen>
+    <dimen name="qs_header_non_clickable_element_height">24sp</dimen>
+    <dimen name="new_qs_header_non_clickable_element_height">24sp</dimen>
 
     <dimen name="qs_footer_padding">20dp</dimen>
     <dimen name="qs_security_footer_height">88dp</dimen>
@@ -822,7 +825,7 @@
 
     <!-- Padding between the mobile signal indicator and the start icon when the roaming icon
          is displayed in the upper left corner. -->
-    <dimen name="roaming_icon_start_padding">2dp</dimen>
+    <dimen name="roaming_icon_start_padding">2sp</dimen>
 
     <!-- Extra padding between the mobile data type icon and the strength indicator when the data
          type icon is wide for the tile in quick settings. -->
@@ -1042,13 +1045,13 @@
     <dimen name="display_cutout_margin_consumption">0px</dimen>
 
     <!-- Height of the Ongoing App Ops chip -->
-    <dimen name="ongoing_appops_chip_height">24dp</dimen>
+    <dimen name="ongoing_appops_chip_height">24sp</dimen>
     <!-- Side padding between background of Ongoing App Ops chip and content -->
     <dimen name="ongoing_appops_chip_side_padding">8dp</dimen>
     <!-- Margin between icons of Ongoing App Ops chip -->
     <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
     <!-- Icon size of Ongoing App Ops chip -->
-    <dimen name="ongoing_appops_chip_icon_size">16dp</dimen>
+    <dimen name="ongoing_appops_chip_icon_size">16sp</dimen>
     <!-- Radius of Ongoing App Ops chip corners -->
     <dimen name="ongoing_appops_chip_bg_corner_radius">28dp</dimen>
     <!--  One or two privacy items  -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6b461ee..c57fef1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3038,6 +3038,19 @@
     -->
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2">&#8226; At least one device is available</string>
 
+    <!---
+    Requirement for the notes app to be available for the user to use. This is shown as part of a
+    bulleted list of requirements. When all requirements are met, the app can be accessed through a
+    shortcut button on the lock screen. [CHAR LIMIT=NONE] -->
+    <string name="keyguard_affordance_enablement_dialog_notes_app_instruction">Select a default notes app to use the notetaking shortcut</string>
+
+    <!---
+    The action to make the lock screen shortcut for the notes app to be available for the user to
+    use. This is shown as the action button in the dialog listing the requirements. When all
+    requirements are met, the app can be accessed through a shortcut button on the lock screen.
+    [CHAR LIMIT=NONE] -->
+    <string name="keyguard_affordance_enablement_dialog_notes_app_action">Select app</string>
+
     <!--
     Error message shown when a shortcut must be pressed and held to activate it, usually shown when
     the user tried to tap the shortcut or held it for too short a time. [CHAR LIMIT=32].
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 016d573..4a66562 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -230,7 +230,7 @@
 
         private ClassLoader getParentClassLoader(ClassLoader baseClassLoader) {
             return new PluginManagerImpl.ClassLoaderFilter(
-                    baseClassLoader, "com.android.systemui.plugin");
+                    baseClassLoader, "com.android.systemui.log", "com.android.systemui.plugin");
         }
 
         /** Returns class loader specific for the given plugin. */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
index 2f9f5b2..1e668b8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
@@ -248,19 +248,23 @@
     // This allows plugins to include any libraries or copied code they want by only including
     // classes from the plugin library.
     static class ClassLoaderFilter extends ClassLoader {
-        private final String mPackage;
+        private final String[] mPackages;
         private final ClassLoader mBase;
 
-        public ClassLoaderFilter(ClassLoader base, String pkg) {
+        ClassLoaderFilter(ClassLoader base, String... pkgs) {
             super(ClassLoader.getSystemClassLoader());
             mBase = base;
-            mPackage = pkg;
+            mPackages = pkgs;
         }
 
         @Override
         protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
-            if (!name.startsWith(mPackage)) super.loadClass(name, resolve);
-            return mBase.loadClass(name);
+            for (String pkg : mPackages) {
+                if (name.startsWith(pkg)) {
+                    return mBase.loadClass(name);
+                }
+            }
+            return super.loadClass(name, resolve);
         }
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index 362d7a9..7cf3121 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -68,7 +68,7 @@
         onActivityLaunchOnSecondaryDisplayRerouted();
     }
 
-    default void onTaskProfileLocked(RunningTaskInfo taskInfo) { }
+    default void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId) { }
     default void onTaskCreated(int taskId, ComponentName componentName) { }
     default void onTaskRemoved(int taskId) { }
     default void onTaskMovedToFront(int taskId) { }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index dd52cfb..c613afb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -262,8 +262,8 @@
         }
 
         @Override
-        public void onTaskProfileLocked(RunningTaskInfo taskInfo) {
-            mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskInfo).sendToTarget();
+        public void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId) {
+            mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, userId, 0, taskInfo).sendToTarget();
         }
 
         @Override
@@ -418,8 +418,9 @@
                     }
                     case ON_TASK_PROFILE_LOCKED: {
                         final RunningTaskInfo info = (RunningTaskInfo) msg.obj;
+                        final int userId = msg.arg1;
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onTaskProfileLocked(info);
+                            mTaskStackListeners.get(i).onTaskProfileLocked(info, userId);
                         }
                         break;
                     }
diff --git a/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt b/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt
new file mode 100644
index 0000000..af29b05
--- /dev/null
+++ b/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log
+
+import android.os.Build
+import android.util.Log
+import android.util.Log.LOG_ID_MAIN
+
+/**
+ * A simplified debug logger built as a wrapper around Android's [Log]. Internal for development.
+ *
+ * The main advantages are:
+ * - Sensible defaults, automatically retrieving the class name from the call-site (i.e., tag);
+ * - The messages are purged from source on release builds (keep in mind they are visible on AOSP);
+ * - Lazily evaluate Strings for zero impact in production builds or when disabled;
+ *
+ * Usage example:
+ * ```kotlin
+ * // Logging a message:
+ * debugLog { "message" }
+ *
+ * // Logging an error:
+ * debugLog(error = exception) { "message" }
+ *
+ * // Logging the current stack trace, for debugging:
+ * debugLog(error = Throwable()) { "message" }
+ * ```
+ */
+object DebugLogger {
+
+    /**
+     * Log a debug message, with sensible defaults.
+     *
+     * For example:
+     * ```kotlin
+     * val one = 1
+     * debugLog { "message#$one" }
+     * ```
+     *
+     * The output will be: `D/NoteTaskController: message#1`
+     *
+     * Beware, the [debugLog] content is **REMOVED FROM SOURCE AND BINARY** in Release builds.
+     *
+     * @param enabled: whether or not the message should be logged. By default, it is
+     *   [Build.IS_DEBUGGABLE].
+     * @param priority: type of this log. By default, it is [Log.DEBUG].
+     * @param tag: identifies the source of a log. By default, it is the receiver's simple name.
+     * @param error: a [Throwable] to log.
+     * @param message: a lazily evaluated message you wish to log.
+     */
+    inline fun Any.debugLog(
+        enabled: Boolean = Build.IS_DEBUGGABLE,
+        priority: Int = Log.DEBUG,
+        tag: String = this::class.simpleName.orEmpty(),
+        error: Throwable? = null,
+        message: () -> String,
+    ) {
+        if (enabled) {
+            if (error == null) {
+                Log.println(priority, tag, message())
+            } else {
+                Log.printlns(LOG_ID_MAIN, priority, tag, message(), error)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt b/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt
new file mode 100644
index 0000000..2764a1f
--- /dev/null
+++ b/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.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.systemui.log
+
+import android.os.Build
+import android.util.Log
+
+/** An empty logger for release builds. */
+object DebugLogger {
+
+    @JvmName("logcatMessage")
+    inline fun Any.debugLog(
+        enabled: Boolean = Build.IS_DEBUGGABLE,
+        priority: Int = Log.DEBUG,
+        tag: String = this::class.simpleName.orEmpty(),
+        error: Throwable? = null,
+        message: () -> String,
+    ) {
+        // no-op.
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1c321ef..7d7b276 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -522,6 +522,14 @@
                     FACE_AUTH_TRIGGERED_TRUST_DISABLED);
         }
 
+        mLogger.logTrustChanged(wasTrusted, enabled, userId);
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onTrustChanged(userId);
+            }
+        }
+
         if (enabled) {
             String message = null;
             if (KeyguardUpdateMonitor.getCurrentUser() == userId
@@ -560,14 +568,6 @@
                 }
             }
         }
-
-        mLogger.logTrustChanged(wasTrusted, enabled, userId);
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onTrustChanged(userId);
-            }
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index a3e7d71..48805be 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -110,6 +110,10 @@
         if (showScanningAnimNow == showScanningAnim) {
             return
         }
+        logger.cameraProtectionShownOrHidden(keyguardUpdateMonitor.isFaceDetectionRunning,
+                authController.isShowing,
+                show,
+                showScanningAnim)
         showScanningAnim = showScanningAnimNow
         updateProtectionBoundingPath()
         // Delay the relayout until the end of the animation when hiding,
@@ -352,6 +356,7 @@
             if (biometricSourceType == BiometricSourceType.FACE) {
                 post {
                     faceAuthSucceeded = true
+                    logger.biometricEvent("biometricAuthenticated")
                     enableShowProtection(true)
                 }
             }
@@ -372,6 +377,7 @@
             if (biometricSourceType == BiometricSourceType.FACE) {
                 post {
                     faceAuthSucceeded = false
+                    logger.biometricEvent("biometricFailed")
                     enableShowProtection(false)
                 }
             }
@@ -385,6 +391,7 @@
             if (biometricSourceType == BiometricSourceType.FACE) {
                 post {
                     faceAuthSucceeded = false
+                    logger.biometricEvent("biometricError")
                     enableShowProtection(false)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index adc0412..ea0f343 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -90,6 +90,8 @@
 import com.android.systemui.util.concurrency.ThreadFactory;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Pair;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -98,8 +100,6 @@
 
 import javax.inject.Inject;
 
-import kotlin.Pair;
-
 /**
  * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
  * for antialiasing and emulation purposes.
@@ -254,11 +254,13 @@
             new CameraAvailabilityListener.CameraTransitionCallback() {
         @Override
         public void onApplyCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
+            mLogger.cameraProtectionEvent("onApplyCameraProtection");
             showCameraProtection(protectionPath, bounds);
         }
 
         @Override
         public void onHideCameraProtection() {
+            mLogger.cameraProtectionEvent("onHideCameraProtection");
             hideCameraProtection();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 4b5c50f..5499d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -19,7 +19,6 @@
 import android.annotation.RawRes
 import android.content.Context
 import android.content.Context.FINGERPRINT_SERVICE
-import android.content.res.Configuration
 import android.hardware.fingerprint.FingerprintManager
 import android.view.DisplayInfo
 import android.view.Surface
@@ -35,21 +34,18 @@
 import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
 import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
 import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
-import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
-import com.android.systemui.unfold.updates.FoldProvider
 
 /** Fingerprint only icon animator for BiometricPrompt.  */
 open class AuthBiometricFingerprintIconController(
         context: Context,
         iconView: LottieAnimationView,
         protected val iconViewOverlay: LottieAnimationView
-) : AuthIconController(context, iconView), FoldProvider.FoldCallback {
+) : AuthIconController(context, iconView) {
 
-    private var isDeviceFolded: Boolean = false
     private val isSideFps: Boolean
     private val isReverseDefaultRotation =
             context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
-    private val screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
+
     var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
         set(value) {
             if (field == value) {
@@ -77,20 +73,16 @@
         if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
             iconView.rotation = 180f
         }
-        screenSizeFoldProvider.registerCallback(this, context.mainExecutor)
-        screenSizeFoldProvider.onConfigurationChange(context.resources.configuration)
     }
 
     private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) {
         val displayInfo = DisplayInfo()
         context.display?.getDisplayInfo(displayInfo)
         val rotation = getRotationFromDefault(displayInfo.rotation)
-        val iconAnimation = getSideFpsAnimationForTransition(rotation)
         val iconViewOverlayAnimation =
                 getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
 
         if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
-            iconView.setAnimation(iconAnimation)
             iconViewOverlay.setAnimation(iconViewOverlayAnimation)
         }
 
@@ -132,10 +124,6 @@
         LottieColorUtils.applyDynamicColors(context, iconView)
     }
 
-    override fun onConfigurationChanged(newConfig: Configuration) {
-        screenSizeFoldProvider.onConfigurationChange(newConfig)
-    }
-
     override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
         if (isSideFps) {
             updateIconSideFps(lastState, newState)
@@ -230,25 +218,6 @@
             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
-        }
-    }
-
-    @RawRes
     private fun getSideFpsOverlayAnimationForTransition(
             @BiometricState oldState: Int,
             @BiometricState newState: Int,
@@ -357,8 +326,4 @@
             )
         }
     }
-
-    override fun onFoldUpdated(isFolded: Boolean) {
-        isDeviceFolded = isFolded
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index e04dd06..f330c34 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -124,6 +124,7 @@
     protected final int mTextColorHint;
 
     private AuthPanelController mPanelController;
+
     private PromptInfo mPromptInfo;
     private boolean mRequireConfirmation;
     private int mUserId;
@@ -266,11 +267,9 @@
     /** Create the controller for managing the icons transitions during the prompt.*/
     @NonNull
     protected abstract AuthIconController createIconController();
-
     void setPanelController(AuthPanelController panelController) {
         mPanelController = panelController;
     }
-
     void setPromptInfo(PromptInfo promptInfo) {
         mPromptInfo = promptInfo;
     }
@@ -463,9 +462,16 @@
         return false;
     }
 
+    /**
+     * Updates mIconView animation on updates to fold state, device rotation, or rear display mode
+     * @param animation new asset to use for iconw
+     */
+    public void updateIconViewAnimation(int animation) {
+        mIconView.setAnimation(animation);
+    }
+
     public void updateState(@BiometricState int newState) {
         Log.v(TAG, "newState: " + newState);
-
         mIconController.updateState(mState, newState);
 
         switch (newState) {
@@ -625,7 +631,6 @@
     public void restoreState(@Nullable Bundle savedState) {
         mSavedState = savedState;
     }
-
     private void setTextOrHide(TextView view, CharSequence charSequence) {
         if (TextUtils.isEmpty(charSequence)) {
             view.setVisibility(View.GONE);
@@ -658,7 +663,6 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        mIconController.onConfigurationChanged(newConfig);
         if (mSavedState != null) {
             updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index aeebb01..b386bc9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -67,6 +67,8 @@
 import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
 import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
 import com.android.systemui.biometrics.ui.CredentialView;
+import com.android.systemui.biometrics.ui.binder.AuthBiometricFingerprintViewBinder;
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -126,6 +128,8 @@
 
     // TODO: these should be migrated out once ready
     private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
+    private final Provider<AuthBiometricFingerprintViewModel>
+            mAuthBiometricFingerprintViewModelProvider;
     private final Provider<CredentialViewModel> mCredentialViewModelProvider;
 
     @VisibleForTesting final BiometricCallback mBiometricCallback;
@@ -241,12 +245,14 @@
                 @NonNull LockPatternUtils lockPatternUtils,
                 @NonNull InteractionJankMonitor jankMonitor,
                 @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+                @NonNull Provider<AuthBiometricFingerprintViewModel>
+                        authBiometricFingerprintViewModelProvider,
                 @NonNull Provider<CredentialViewModel> credentialViewModelProvider) {
             mConfig.mSensorIds = sensorIds;
             return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
                     panelInteractionDetector, userManager, lockPatternUtils, jankMonitor,
-                    biometricPromptInteractor, credentialViewModelProvider,
-                    new Handler(Looper.getMainLooper()), bgExecutor);
+                    biometricPromptInteractor, authBiometricFingerprintViewModelProvider,
+                    credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor);
         }
     }
 
@@ -339,6 +345,8 @@
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull InteractionJankMonitor jankMonitor,
             @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+            @NonNull Provider<AuthBiometricFingerprintViewModel>
+                    authBiometricFingerprintViewModelProvider,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull Handler mainHandler,
             @NonNull @Background DelayableExecutor bgExecutor) {
@@ -368,6 +376,7 @@
         mBackgroundExecutor = bgExecutor;
         mInteractionJankMonitor = jankMonitor;
         mBiometricPromptInteractor = biometricPromptInteractor;
+        mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
         mCredentialViewModelProvider = credentialViewModelProvider;
 
         // Inflate biometric view only if necessary.
@@ -386,6 +395,9 @@
                 fingerprintAndFaceView.updateOverrideIconLayoutParamsSize();
                 fingerprintAndFaceView.setFaceClass3(
                         faceProperties.sensorStrength == STRENGTH_STRONG);
+                final AuthBiometricFingerprintViewModel fpAndFaceViewModel =
+                        mAuthBiometricFingerprintViewModelProvider.get();
+                AuthBiometricFingerprintViewBinder.bind(fingerprintAndFaceView, fpAndFaceViewModel);
                 mBiometricView = fingerprintAndFaceView;
             } else if (fpProperties != null) {
                 final AuthBiometricFingerprintView fpView =
@@ -394,6 +406,9 @@
                 fpView.setSensorProperties(fpProperties);
                 fpView.setScaleFactorProvider(config.mScaleProvider);
                 fpView.updateOverrideIconLayoutParamsSize();
+                final AuthBiometricFingerprintViewModel fpViewModel =
+                        mAuthBiometricFingerprintViewModelProvider.get();
+                AuthBiometricFingerprintViewBinder.bind(fpView, fpViewModel);
                 mBiometricView = fpView;
             } else if (faceProperties != null) {
                 mBiometricView = (AuthBiometricFaceView) layoutInflater.inflate(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 6eb3c70..3579e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -73,6 +73,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -127,6 +128,9 @@
 
     // TODO: these should be migrated out once ready
     @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
+
+    @NonNull private final Provider<AuthBiometricFingerprintViewModel>
+            mAuthBiometricFingerprintViewModelProvider;
     @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider;
     @NonNull private final LogContextInteractor mLogContextInteractor;
 
@@ -722,7 +726,6 @@
         }
         onDialogDismissed(reason);
     }
-
     @Inject
     public AuthController(Context context,
             Execution execution,
@@ -741,6 +744,8 @@
             @NonNull UdfpsLogger udfpsLogger,
             @NonNull LogContextInteractor logContextInteractor,
             @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+            @NonNull Provider<AuthBiometricFingerprintViewModel>
+                    authBiometricFingerprintViewModelProvider,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull InteractionJankMonitor jankMonitor,
             @Main Handler handler,
@@ -771,6 +776,7 @@
 
         mLogContextInteractor = logContextInteractor;
         mBiometricPromptInteractor = biometricPromptInteractor;
+        mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
         mCredentialViewModelProvider = credentialViewModelProvider;
 
         mOrientationListener = new BiometricDisplayListener(
@@ -1299,7 +1305,7 @@
                 .build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle,
                         panelInteractionDetector, userManager, lockPatternUtils,
                         mInteractionJankMonitor, mBiometricPromptInteractor,
-                        mCredentialViewModelProvider);
+                        mAuthBiometricFingerprintViewModelProvider, mCredentialViewModelProvider);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
index d6ad4da..f5f4640 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
@@ -18,7 +18,6 @@
 
 import android.annotation.DrawableRes
 import android.content.Context
-import android.content.res.Configuration
 import android.graphics.drawable.Animatable2
 import android.graphics.drawable.AnimatedVectorDrawable
 import android.graphics.drawable.Drawable
@@ -94,8 +93,6 @@
     /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
     open fun handleAnimationEnd(drawable: Drawable) {}
 
-    open fun onConfigurationChanged(newConfig: Configuration) {}
-
     // TODO(b/251476085): Migrate this to an extension at the appropriate level?
     /** Load the given [rawResources] immediately so they are cached for use in the [context]. */
     protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 4319f01..962140f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -55,6 +55,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -84,6 +85,7 @@
     private val activityTaskManager: ActivityTaskManager,
     overviewProxyService: OverviewProxyService,
     displayManager: DisplayManager,
+    private val displayStateInteractor: DisplayStateInteractor,
     @Main private val mainExecutor: DelayableExecutor,
     @Main private val handler: Handler,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -203,14 +205,16 @@
         request: SideFpsUiRequestSource,
         @BiometricOverlayConstants.ShowReason reason: Int = BiometricOverlayConstants.REASON_UNKNOWN
     ) {
-        requests.add(request)
-        mainExecutor.execute {
-            if (overlayView == null) {
-                traceSection("SideFpsController#show(request=${request.name}, reason=$reason") {
-                    createOverlayForDisplay(reason)
+        if (!displayStateInteractor.isInRearDisplayMode.value) {
+            requests.add(request)
+            mainExecutor.execute {
+                if (overlayView == null) {
+                    traceSection("SideFpsController#show(request=${request.name}, reason=$reason") {
+                        createOverlayForDisplay(reason)
+                    }
+                } else {
+                    Log.v(TAG, "overlay already shown")
                 }
-            } else {
-                Log.v(TAG, "overlay already shown")
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index f0b9f67..c831663 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -21,8 +21,12 @@
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
 import com.android.systemui.biometrics.data.repository.PromptRepository
 import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
+import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl
 import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
 import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
@@ -45,6 +49,9 @@
     @SysUISingleton
     fun fingerprintRepository(impl: FingerprintPropertyRepositoryImpl):
             FingerprintPropertyRepository
+    @Binds
+    @SysUISingleton
+    fun rearDisplayStateRepository(impl: RearDisplayStateRepositoryImpl): RearDisplayStateRepository
 
     @Binds
     @SysUISingleton
@@ -52,6 +59,10 @@
 
     @Binds
     @SysUISingleton
+    fun providesDisplayStateInteractor(impl: DisplayStateInteractorImpl): DisplayStateInteractor
+
+    @Binds
+    @SysUISingleton
     fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt
new file mode 100644
index 0000000..d17d961
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.biometrics.data.repository
+
+import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import com.android.internal.util.ArrayUtils
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Provide current rear display state. */
+interface RearDisplayStateRepository {
+    /** Provides the current rear display state. */
+    val isInRearDisplayMode: StateFlow<Boolean>
+}
+
+@SysUISingleton
+class RearDisplayStateRepositoryImpl
+@Inject
+constructor(
+    @Application applicationScope: CoroutineScope,
+    @Application context: Context,
+    deviceStateManager: DeviceStateManager,
+    @Main mainExecutor: Executor
+) : RearDisplayStateRepository {
+    override val isInRearDisplayMode: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val sendRearDisplayStateUpdate = { state: Boolean ->
+                    trySendWithFailureLogging(
+                        state,
+                        TAG,
+                        "Error sending rear display state update to $state"
+                    )
+                }
+
+                val callback =
+                    DeviceStateManager.DeviceStateCallback { state ->
+                        val isInRearDisplayMode =
+                            ArrayUtils.contains(
+                                context.resources.getIntArray(
+                                    com.android.internal.R.array.config_rearDisplayDeviceStates
+                                ),
+                                state
+                            )
+                        sendRearDisplayStateUpdate(isInRearDisplayMode)
+                    }
+
+                sendRearDisplayStateUpdate(false)
+                deviceStateManager.registerCallback(mainExecutor, callback)
+                awaitClose { deviceStateManager.unregisterCallback(callback) }
+            }
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
+    companion object {
+        const val TAG = "RearDisplayStateRepositoryImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
new file mode 100644
index 0000000..c935aa2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
+import com.android.systemui.unfold.updates.FoldProvider
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Aggregates display state information. */
+interface DisplayStateInteractor {
+
+    /** Whether the device is currently in rear display mode. */
+    val isInRearDisplayMode: StateFlow<Boolean>
+
+    /** Whether the device is currently folded. */
+    val isFolded: Flow<Boolean>
+
+    /** Called on configuration changes, used to keep the display state in sync */
+    fun onConfigurationChanged(newConfig: Configuration)
+}
+
+/** Encapsulates logic for interacting with the display state. */
+class DisplayStateInteractorImpl
+@Inject
+constructor(
+    @Application applicationScope: CoroutineScope,
+    @Application context: Context,
+    @Main mainExecutor: Executor,
+    rearDisplayStateRepository: RearDisplayStateRepository,
+) : DisplayStateInteractor {
+    private var screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
+
+    fun setScreenSizeFoldProvider(foldProvider: ScreenSizeFoldProvider) {
+        screenSizeFoldProvider = foldProvider
+    }
+
+    override val isFolded: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val sendFoldStateUpdate = { state: Boolean ->
+                    trySendWithFailureLogging(
+                        state,
+                        TAG,
+                        "Error sending fold state update to $state"
+                    )
+                }
+
+                val callback =
+                    object : FoldProvider.FoldCallback {
+                        override fun onFoldUpdated(isFolded: Boolean) {
+                            sendFoldStateUpdate(isFolded)
+                        }
+                    }
+                sendFoldStateUpdate(false)
+                screenSizeFoldProvider.registerCallback(callback, mainExecutor)
+                awaitClose { screenSizeFoldProvider.unregisterCallback(callback) }
+            }
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
+    override val isInRearDisplayMode: StateFlow<Boolean> =
+        rearDisplayStateRepository.isInRearDisplayMode
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        screenSizeFoldProvider.onConfigurationChange(newConfig)
+    }
+
+    companion object {
+        private const val TAG = "DisplayStateInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt
new file mode 100644
index 0000000..e776ab4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.biometrics.ui.binder
+
+import android.view.Surface
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.biometrics.AuthBiometricFingerprintView
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+object AuthBiometricFingerprintViewBinder {
+
+    /** Binds a [AuthBiometricFingerprintView] to a [AuthBiometricFingerprintViewModel]. */
+    @JvmStatic
+    fun bind(view: AuthBiometricFingerprintView, viewModel: AuthBiometricFingerprintViewModel) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.onConfigurationChanged(view.context.resources.configuration)
+                viewModel.setRotation(view.context.display?.orientation ?: Surface.ROTATION_0)
+                launch {
+                    viewModel.iconAsset.collect { iconAsset ->
+                        view.updateIconViewAnimation(iconAsset)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModel.kt
new file mode 100644
index 0000000..617d80c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModel.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import android.annotation.RawRes
+import android.content.res.Configuration
+import android.view.Surface
+import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Models UI of AuthBiometricFingerprintView to support rear display state changes. */
+class AuthBiometricFingerprintViewModel
+@Inject
+constructor(private val interactor: DisplayStateInteractor) {
+    /** Current device rotation. */
+    private var rotation: Int = Surface.ROTATION_0
+
+    /** Current AuthBiometricFingerprintView asset. */
+    val iconAsset: Flow<Int> =
+        combine(interactor.isFolded, interactor.isInRearDisplayMode) {
+            isFolded: Boolean,
+            isInRearDisplayMode: Boolean ->
+            getSideFpsAnimationAsset(isFolded, isInRearDisplayMode)
+        }
+
+    @RawRes
+    private fun getSideFpsAnimationAsset(
+        isDeviceFolded: Boolean,
+        isInRearDisplayMode: Boolean,
+    ): Int =
+        when (rotation) {
+            Surface.ROTATION_90 ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_portrait_reverse_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_topleft
+                } else {
+                    R.raw.biometricprompt_portrait_base_topleft
+                }
+            Surface.ROTATION_270 ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_portrait_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_bottomright
+                } else {
+                    R.raw.biometricprompt_portrait_base_bottomright
+                }
+            else ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_landscape_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_default
+                } else {
+                    R.raw.biometricprompt_landscape_base
+                }
+        }
+
+    /** Called on configuration changes */
+    fun onConfigurationChanged(newConfig: Configuration) {
+        interactor.onConfigurationChanged(newConfig)
+    }
+
+    fun setRotation(newRotation: Int) {
+        rotation = newRotation
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
new file mode 100644
index 0000000..0542e13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.clipboardoverlay
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.net.Uri
+import android.util.Log
+import android.util.Size
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import java.io.IOException
+import java.util.function.Consumer
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
+
+class ClipboardImageLoader
+@Inject
+constructor(
+    private val context: Context,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Application private val mainScope: CoroutineScope
+) {
+    private val TAG: String = "ClipboardImageLoader"
+
+    suspend fun load(uri: Uri, timeoutMs: Long = 300) =
+        withTimeoutOrNull(timeoutMs) {
+            withContext(bgDispatcher) {
+                try {
+                    val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
+                    context.contentResolver.loadThumbnail(uri, Size(size, size * 4), null)
+                } catch (e: IOException) {
+                    Log.e(TAG, "Thumbnail loading failed!", e)
+                    null
+                }
+            }
+        }
+
+    fun loadAsync(uri: Uri, callback: Consumer<Bitmap?>) {
+        mainScope.launch { callback.accept(load(uri)) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 0aeab10..757ebf4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -32,6 +32,7 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -90,6 +91,7 @@
     private final ClipboardOverlayUtils mClipboardUtils;
     private final FeatureFlags mFeatureFlags;
     private final Executor mBgExecutor;
+    private final ClipboardImageLoader mClipboardImageLoader;
 
     private final ClipboardOverlayView mView;
 
@@ -109,6 +111,7 @@
 
     private Runnable mOnUiUpdate;
 
+    private boolean mShowingUi;
     private boolean mIsMinimized;
     private ClipboardModel mClipboardModel;
 
@@ -175,9 +178,11 @@
             FeatureFlags featureFlags,
             ClipboardOverlayUtils clipboardUtils,
             @Background Executor bgExecutor,
+            ClipboardImageLoader clipboardImageLoader,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
+        mClipboardImageLoader = clipboardImageLoader;
 
         mClipboardLogger = new ClipboardLogger(uiEventLogger);
 
@@ -260,21 +265,42 @@
         boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting;
         mClipboardModel = model;
         mClipboardLogger.setClipSource(mClipboardModel.getSource());
-        if (shouldAnimate) {
-            reset();
-            mClipboardLogger.setClipSource(mClipboardModel.getSource());
-            if (shouldShowMinimized(mWindow.getWindowInsets())) {
-                mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED);
-                mIsMinimized = true;
-                mView.setMinimized(true);
-            } else {
-                mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
+        if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+            if (shouldAnimate) {
+                reset();
+                mClipboardLogger.setClipSource(mClipboardModel.getSource());
+                if (shouldShowMinimized(mWindow.getWindowInsets())) {
+                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED);
+                    mIsMinimized = true;
+                    mView.setMinimized(true);
+                } else {
+                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
+                    setExpandedView(this::animateIn);
+                }
+                mView.announceForAccessibility(
+                        getAccessibilityAnnouncement(mClipboardModel.getType()));
+            } else if (!mIsMinimized) {
+                setExpandedView(() -> {
+                });
+            }
+        } else {
+            if (shouldAnimate) {
+                reset();
+                mClipboardLogger.setClipSource(mClipboardModel.getSource());
+                if (shouldShowMinimized(mWindow.getWindowInsets())) {
+                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED);
+                    mIsMinimized = true;
+                    mView.setMinimized(true);
+                } else {
+                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
+                    setExpandedView();
+                    animateIn();
+                }
+                mView.announceForAccessibility(
+                        getAccessibilityAnnouncement(mClipboardModel.getType()));
+            } else if (!mIsMinimized) {
                 setExpandedView();
             }
-            animateIn();
-            mView.announceForAccessibility(getAccessibilityAnnouncement(mClipboardModel.getType()));
-        } else if (!mIsMinimized) {
-            setExpandedView();
         }
         if (mClipboardModel.isRemote()) {
             mTimeoutHandler.cancelTimeout();
@@ -285,6 +311,58 @@
         }
     }
 
+    private void setExpandedView(Runnable onViewReady) {
+        final ClipboardModel model = mClipboardModel;
+        mView.setMinimized(false);
+        switch (model.getType()) {
+            case TEXT:
+                if (model.isRemote() || DeviceConfig.getBoolean(
+                        DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
+                    if (model.getTextLinks() != null) {
+                        classifyText(model);
+                    }
+                }
+                if (model.isSensitive()) {
+                    mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
+                } else {
+                    mView.showTextPreview(model.getText().toString(), false);
+                }
+                mView.setEditAccessibilityAction(true);
+                mOnPreviewTapped = this::editText;
+                onViewReady.run();
+                break;
+            case IMAGE:
+                mView.setEditAccessibilityAction(true);
+                mOnPreviewTapped = () -> editImage(model.getUri());
+                if (model.isSensitive()) {
+                    mView.showImagePreview(null);
+                    onViewReady.run();
+                } else {
+                    mClipboardImageLoader.loadAsync(model.getUri(), (bitmap) -> mView.post(() -> {
+                        if (bitmap == null) {
+                            mView.showDefaultTextPreview();
+                        } else {
+                            mView.showImagePreview(bitmap);
+                        }
+                        onViewReady.run();
+                    }));
+                }
+                break;
+            case URI:
+            case OTHER:
+                mView.showDefaultTextPreview();
+                onViewReady.run();
+                break;
+        }
+        if (!model.isRemote()) {
+            maybeShowRemoteCopy(model.getClipData());
+        }
+        if (model.getType() != ClipboardModel.Type.OTHER) {
+            mOnShareTapped = () -> shareContent(model.getClipData());
+            mView.showShareChip();
+        }
+    }
+
     private void setExpandedView() {
         final ClipboardModel model = mClipboardModel;
         mView.setMinimized(false);
@@ -350,8 +428,12 @@
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED);
                     mIsMinimized = false;
                 }
-                setExpandedView();
-                animateIn();
+                if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+                    setExpandedView(() -> animateIn());
+                } else {
+                    setExpandedView();
+                    animateIn();
+                }
             }
         });
         mEnterAnimator.start();
@@ -412,7 +494,8 @@
                 mInputMonitor.getInputChannel(), Looper.getMainLooper()) {
             @Override
             public void onInputEvent(InputEvent event) {
-                if (event instanceof MotionEvent) {
+                if ((!mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT) || mShowingUi)
+                        && event instanceof MotionEvent) {
                     MotionEvent motionEvent = (MotionEvent) event;
                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
                         if (!mView.isInTouchRegion(
@@ -452,6 +535,12 @@
         mEnterAnimator = mView.getEnterAnimation();
         mEnterAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
+            public void onAnimationStart(Animator animation) {
+                super.onAnimationStart(animation);
+                mShowingUi = true;
+            }
+
+            @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
                 if (mOnUiUpdate != null) {
@@ -518,6 +607,7 @@
         mOnRemoteCopyTapped = null;
         mOnShareTapped = null;
         mOnPreviewTapped = null;
+        mShowingUi = false;
         mView.reset();
         mTimeoutHandler.cancelTimeout();
         mClipboardLogger.reset();
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index af7387b..6ca409f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -206,11 +206,6 @@
             )
 
     /** Whether to inflate the bouncer view on a background thread. */
-    // TODO(b/272091103): Tracking Bug
-    @JvmField
-    val ASYNC_INFLATE_BOUNCER = releasedFlag(229, "async_inflate_bouncer")
-
-    /** Whether to inflate the bouncer view on a background thread. */
     // TODO(b/273341787): Tracking Bug
     @JvmField
     val PREVENT_BYPASS_KEYGUARD = releasedFlag(230, "prevent_bypass_keyguard")
@@ -236,6 +231,11 @@
     val REVAMPED_BOUNCER_MESSAGES =
         unreleasedFlag(234, "revamped_bouncer_messages")
 
+    /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
+    // TODO(b/279794160): Tracking bug.
+    @JvmField
+    val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer")
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -274,7 +274,7 @@
         )
 
     @JvmField
-    val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = false)
+    val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = true)
 
     // TODO(b/278068252): Tracking Bug
     @JvmField
@@ -391,7 +391,7 @@
     val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = releasedFlag(912, "media_ttt_dismiss_gesture")
 
     // TODO(b/266157412): Tracking Bug
-    val MEDIA_RETAIN_SESSIONS = releasedFlag(913, "media_retain_sessions")
+    val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
 
     // TODO(b/266739309): Tracking Bug
     @JvmField
@@ -401,10 +401,10 @@
     val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress")
 
     // TODO(b/267166152) : Tracking Bug
-    val MEDIA_RETAIN_RECOMMENDATIONS = releasedFlag(916, "media_retain_recommendations")
+    val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag(916, "media_retain_recommendations")
 
     // TODO(b/270437894): Tracking Bug
-    val MEDIA_REMOTE_RESUME = releasedFlag(917, "media_remote_resume")
+    val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume")
 
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -600,6 +600,8 @@
 
     // 1700 - clipboard
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
+    // TODO(b/278714186) Tracking Bug
+    @JvmField val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag(1702, "clipboard_image_timeout")
 
     // 1800 - shade container
     // TODO(b/265944639): Tracking Bug
@@ -623,7 +625,7 @@
     // TODO(b/269132640): Tracking Bug
     @JvmField
     val APP_PANELS_REMOVE_APPS_ALLOWED =
-        unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = true)
+        releasedFlag(2003, "app_panels_remove_apps_allowed")
 
     // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
     // TODO(b/259264861): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index b92499e..b7ba201 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -56,11 +56,11 @@
         tscl.registerTaskStackListener(mLockListener);
     }
 
-    private void startWorkChallengeInTask(ActivityManager.RunningTaskInfo info) {
+    private void startWorkChallengeInTask(ActivityManager.RunningTaskInfo info, int userId) {
         String packageName = info.baseActivity != null ? info.baseActivity.getPackageName() : "";
         Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
                 .setComponent(new ComponentName(mContext, WorkLockActivity.class))
-                .putExtra(Intent.EXTRA_USER_ID, info.userId)
+                .putExtra(Intent.EXTRA_USER_ID, userId)
                 .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
                 .addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
                         | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -76,10 +76,11 @@
         } else {
             // Starting the activity inside the task failed. We can't be sure why, so to be
             // safe just remove the whole task if it still exists.
+            Log.w(TAG, "Failed to start work lock activity, will remove task=" + info.taskId);
             try {
                 mIatm.removeTask(info.taskId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed to get description for task=" + info.taskId);
+                Log.e(TAG, "Failed to remove task=" + info.taskId);
             }
         }
     }
@@ -112,8 +113,8 @@
 
     private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() {
         @Override
-        public void onTaskProfileLocked(ActivityManager.RunningTaskInfo info) {
-            startWorkChallengeInTask(info);
+        public void onTaskProfileLocked(ActivityManager.RunningTaskInfo info, int userId) {
+            startWorkChallengeInTask(info, userId);
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 6bbc6f6..c1aefc7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -112,8 +112,6 @@
                         viewModel.isShowing.collect { isShowing ->
                             view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
                             if (isShowing) {
-                                // Reset Security Container entirely.
-                                view.visibility = View.VISIBLE
                                 securityContainerController.reinflateViewFlipper {
                                     // Reset Security Container entirely.
                                     securityContainerController.onBouncerVisibilityChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index a98a7d8..951f0bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -21,6 +21,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.graphics.Rect
 import android.hardware.display.DisplayManager
 import android.os.Bundle
 import android.os.IBinder
@@ -33,6 +34,7 @@
 import com.android.keyguard.ClockEventController
 import com.android.keyguard.KeyguardClockSwitch
 import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -61,6 +63,7 @@
     private val clockRegistry: ClockRegistry,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val lockscreenSmartspaceController: LockscreenSmartspaceController,
+    private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
     @Assisted bundle: Bundle,
 ) {
 
@@ -112,7 +115,9 @@
 
             setUpBottomArea(rootView)
 
-            setupSmartspace(rootView)
+            setUpSmartspace(rootView)
+
+            setUpUdfps(rootView)
 
             setUpClock(rootView)
 
@@ -169,49 +174,53 @@
     /**
      * This sets up and shows a non-interactive smart space
      *
-     * The top padding is as follows:
-     *    Status bar height + clock top margin + keyguard smart space top offset
+     * The top padding is as follows: Status bar height + clock top margin + keyguard smart space
+     * top offset
      *
-     * The start padding is as follows:
-     *    Clock padding start + Below clock padding start
+     * The start padding is as follows: Clock padding start + Below clock padding start
      *
-     * The end padding is as follows:
-     *    Below clock padding end
+     * The end padding is as follows: Below clock padding end
      */
-    private fun setupSmartspace(parentView: ViewGroup) {
-        if (!lockscreenSmartspaceController.isEnabled() ||
-                !lockscreenSmartspaceController.isDateWeatherDecoupled()) {
+    private fun setUpSmartspace(parentView: ViewGroup) {
+        if (
+            !lockscreenSmartspaceController.isEnabled() ||
+                !lockscreenSmartspaceController.isDateWeatherDecoupled()
+        ) {
             return
         }
 
         smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
 
-        val topPadding: Int = with(context.resources) {
-            getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
+        val topPadding: Int =
+            with(context.resources) {
+                getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
                     getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
                     getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
-        }
+            }
 
-        val startPadding: Int = with(context.resources) {
-            getDimensionPixelSize(R.dimen.clock_padding_start) +
+        val startPadding: Int =
+            with(context.resources) {
+                getDimensionPixelSize(R.dimen.clock_padding_start) +
                     getDimensionPixelSize(R.dimen.below_clock_padding_start)
-        }
+            }
 
-        val endPadding: Int = context.resources
-                .getDimensionPixelSize(R.dimen.below_clock_padding_end)
+        val endPadding: Int =
+            context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end)
 
         smartSpaceView?.let {
             it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
             it.isClickable = false
 
             parentView.addView(
-                    it,
-                    FrameLayout.LayoutParams(
-                            FrameLayout.LayoutParams.MATCH_PARENT,
-                            FrameLayout.LayoutParams.WRAP_CONTENT,
-                    ),
+                it,
+                FrameLayout.LayoutParams(
+                    FrameLayout.LayoutParams.MATCH_PARENT,
+                    FrameLayout.LayoutParams.WRAP_CONTENT,
+                ),
             )
         }
+
+        smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
     }
 
     private fun setUpBottomArea(parentView: ViewGroup) {
@@ -234,6 +243,33 @@
         )
     }
 
+    private fun setUpUdfps(parentView: ViewGroup) {
+        val sensorBounds = udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds
+
+        // If sensorBounds are default rect, then there is no UDFPS
+        if (sensorBounds == Rect()) {
+            return
+        }
+
+        // Place the UDFPS view in the proper sensor location
+        val fingerprintLayoutParams =
+            FrameLayout.LayoutParams(sensorBounds.width(), sensorBounds.height())
+        fingerprintLayoutParams.setMarginsRelative(
+            sensorBounds.left,
+            sensorBounds.top,
+            sensorBounds.right,
+            sensorBounds.bottom
+        )
+        val finger =
+            LayoutInflater.from(context)
+                .inflate(
+                    R.layout.udfps_keyguard_view_internal,
+                    parentView,
+                    false,
+                ) as View
+        parentView.addView(finger, fingerprintLayoutParams)
+    }
+
     private fun setUpClock(parentView: ViewGroup) {
         val clockChangeListener =
             object : ClockRegistry.ClockChangeListener {
@@ -266,8 +302,6 @@
         disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
 
         onClockChanged(parentView)
-
-        updateSmartspaceWithSetupClock()
     }
 
     private fun onClockChanged(parentView: ViewGroup) {
@@ -281,33 +315,22 @@
                 ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
 
             clockView?.let { parentView.removeView(it) }
-            clockView = largeClock?.view?.apply {
-                if (shouldHighlightSelectedAffordance) {
-                    alpha = DIM_ALPHA
+            clockView =
+                largeClock?.view?.apply {
+                    if (shouldHighlightSelectedAffordance) {
+                        alpha = DIM_ALPHA
+                    }
+                    parentView.addView(this)
+                    visibility = View.VISIBLE
                 }
-                parentView.addView(this)
-                visibility = View.VISIBLE
-            }
         } else {
             clockView?.visibility = View.GONE
         }
-    }
 
-    /**
-     * Updates smart space after clock is set up. Used to show or hide smartspace with the right
-     * opacity based on the clock after setup.
-     */
-    private fun updateSmartspaceWithSetupClock() {
+        // Hide smart space if the clock has weather display; otherwise show it
         val hasCustomWeatherDataDisplay =
-                clockController
-                        .clock
-                        ?.largeClock
-                        ?.config
-                        ?.hasCustomWeatherDataDisplay == true
-
+            clockController.clock?.largeClock?.config?.hasCustomWeatherDataDisplay == true
         hideSmartspace(hasCustomWeatherDataDisplay)
-
-        smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
index edc278d..4300f37 100644
--- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel.DEBUG
 import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
 private const val TAG = "ScreenDecorationsLog"
@@ -131,4 +132,36 @@
     fun onSensorLocationChanged() {
         logBuffer.log(TAG, DEBUG, "AuthControllerCallback in ScreenDecorations triggered")
     }
+
+    fun cameraProtectionShownOrHidden(
+        faceDetectionRunning: Boolean,
+        biometricPromptShown: Boolean,
+        requestedState: Boolean,
+        currentlyShowing: Boolean
+    ) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                bool1 = faceDetectionRunning
+                bool2 = biometricPromptShown
+                bool3 = requestedState
+                bool4 = currentlyShowing
+            },
+            {
+                "isFaceDetectionRunning: $bool1, " +
+                    "isBiometricPromptShowing: $bool2, " +
+                    "requestedState: $bool3, " +
+                    "currentState: $bool4"
+            }
+        )
+    }
+
+    fun biometricEvent(@CompileTimeConstant info: String) {
+        logBuffer.log(TAG, DEBUG, info)
+    }
+
+    fun cameraProtectionEvent(@CompileTimeConstant cameraProtectionEvent: String) {
+        logBuffer.log(TAG, DEBUG, cameraProtectionEvent)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
index 42fdd68..592044e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
@@ -27,19 +27,27 @@
  */
 data class TableChange(
     var timestamp: Long = 0,
-    var columnPrefix: String = "",
-    var columnName: String = "",
-    var isInitial: Boolean = false,
-    var type: DataType = DataType.EMPTY,
-    var bool: Boolean = false,
-    var int: Int? = null,
-    var str: String? = null,
+    private var columnPrefix: String = "",
+    private var columnName: String = "",
+    private var isInitial: Boolean = false,
+    private var type: DataType = DataType.EMPTY,
+    private var bool: Boolean = false,
+    private var int: Int? = null,
+    private var str: String? = null,
 ) {
+    init {
+        // Truncate any strings that were passed into the constructor. [reset] and [set] will take
+        // care of the rest of the truncation.
+        this.columnPrefix = columnPrefix.take(MAX_STRING_LENGTH)
+        this.columnName = columnName.take(MAX_STRING_LENGTH)
+        this.str = str?.take(MAX_STRING_LENGTH)
+    }
+
     /** Resets to default values so that the object can be recycled. */
     fun reset(timestamp: Long, columnPrefix: String, columnName: String, isInitial: Boolean) {
         this.timestamp = timestamp
-        this.columnPrefix = columnPrefix
-        this.columnName = columnName
+        this.columnPrefix = columnPrefix.take(MAX_STRING_LENGTH)
+        this.columnName = columnName.take(MAX_STRING_LENGTH)
         this.isInitial = isInitial
         this.type = DataType.EMPTY
         this.bool = false
@@ -50,7 +58,7 @@
     /** Sets this to store a string change. */
     fun set(value: String?) {
         type = DataType.STRING
-        str = value
+        str = value?.take(MAX_STRING_LENGTH)
     }
 
     /** Sets this to store a boolean change. */
@@ -89,6 +97,8 @@
         }
     }
 
+    fun getColumnName() = columnName
+
     fun getVal(): String {
         val value =
             when (type) {
@@ -109,5 +119,8 @@
 
     companion object {
         @VisibleForTesting const val IS_INITIAL_PREFIX = "**"
+        // Don't allow any strings larger than this length so that we have a hard upper limit on the
+        // size of the data stored by the buffer.
+        @VisibleForTesting const val MAX_STRING_LENGTH = 500
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 9d883cc..8babfc9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -291,7 +291,7 @@
     private fun echoToDesiredEndpoints(change: TableChange) {
         if (
             logcatEchoTracker.isBufferLoggable(bufferName = name, LogLevel.DEBUG) ||
-                logcatEchoTracker.isTagLoggable(change.columnName, LogLevel.DEBUG)
+                logcatEchoTracker.isTagLoggable(change.getColumnName(), LogLevel.DEBUG)
         ) {
             if (change.hasData()) {
                 localLogcat.d(name, change.logcatRepresentation())
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 9997730..fa42114 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -43,7 +43,6 @@
 import android.net.Uri
 import android.os.Parcelable
 import android.os.Process
-import android.os.RemoteException
 import android.os.UserHandle
 import android.provider.Settings
 import android.service.notification.StatusBarNotification
@@ -53,7 +52,6 @@
 import android.util.Pair as APair
 import androidx.media.utils.MediaConstants
 import com.android.internal.logging.InstanceId
-import com.android.internal.statusbar.IStatusBarService
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Dumpable
 import com.android.systemui.R
@@ -139,8 +137,6 @@
         expiryTimeMs = 0,
     )
 
-const val MEDIA_TITLE_ERROR_MESSAGE = "Invalid media data: title is null or blank."
-
 fun isMediaNotification(sbn: StatusBarNotification): Boolean {
     return sbn.notification.isMediaNotification()
 }
@@ -185,7 +181,6 @@
     private val logger: MediaUiEventLogger,
     private val smartspaceManager: SmartspaceManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val statusBarService: IStatusBarService,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     companion object {
@@ -257,7 +252,6 @@
         mediaFlags: MediaFlags,
         logger: MediaUiEventLogger,
         smartspaceManager: SmartspaceManager,
-        statusBarService: IStatusBarService,
         keyguardUpdateMonitor: KeyguardUpdateMonitor,
     ) : this(
         context,
@@ -283,7 +277,6 @@
         logger,
         smartspaceManager,
         keyguardUpdateMonitor,
-        statusBarService,
     )
 
     private val appChangeReceiver =
@@ -385,21 +378,21 @@
 
     fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
         if (useQsMediaPlayer && isMediaNotification(sbn)) {
-            var isNewlyActiveEntry = false
+            var logEvent = false
             Assert.isMainThread()
             val oldKey = findExistingEntry(key, sbn.packageName)
             if (oldKey == null) {
                 val instanceId = logger.getNewInstanceId()
                 val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
                 mediaEntries.put(key, temp)
-                isNewlyActiveEntry = true
+                logEvent = true
             } else if (oldKey != key) {
                 // Resume -> active conversion; move to new key
                 val oldData = mediaEntries.remove(oldKey)!!
-                isNewlyActiveEntry = true
+                logEvent = true
                 mediaEntries.put(key, oldData)
             }
-            loadMediaData(key, sbn, oldKey, isNewlyActiveEntry)
+            loadMediaData(key, sbn, oldKey, logEvent)
         } else {
             onNotificationRemoved(key)
         }
@@ -482,9 +475,9 @@
         key: String,
         sbn: StatusBarNotification,
         oldKey: String?,
-        isNewlyActiveEntry: Boolean = false,
+        logEvent: Boolean = false
     ) {
-        backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+        backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, logEvent) }
     }
 
     /** Add a listener for changes in this class */
@@ -608,11 +601,9 @@
         }
     }
 
-    private fun removeEntry(key: String, logEvent: Boolean = true) {
+    private fun removeEntry(key: String) {
         mediaEntries.remove(key)?.let {
-            if (logEvent) {
-                logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
-            }
+            logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
         }
         notifyMediaDataRemoved(key)
     }
@@ -760,7 +751,7 @@
         key: String,
         sbn: StatusBarNotification,
         oldKey: String?,
-        isNewlyActiveEntry: Boolean = false,
+        logEvent: Boolean = false
     ) {
         val token =
             sbn.notification.extras.getParcelable(
@@ -774,34 +765,6 @@
         val metadata = mediaController.metadata
         val notif: Notification = sbn.notification
 
-        // Song name
-        var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
-        if (song == null) {
-            song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
-        }
-        if (song == null) {
-            song = HybridGroupManager.resolveTitle(notif)
-        }
-        // Media data must have a title.
-        if (song.isNullOrBlank()) {
-            try {
-                statusBarService.onNotificationError(
-                    sbn.packageName,
-                    sbn.tag,
-                    sbn.id,
-                    sbn.uid,
-                    sbn.initialPid,
-                    MEDIA_TITLE_ERROR_MESSAGE,
-                    sbn.user.identifier
-                )
-            } catch (e: RemoteException) {
-                Log.e(TAG, "cancelNotification failed: $e")
-            }
-            // Only add log for media removed if active media is updated with invalid title.
-            foregroundExecutor.execute { removeEntry(key, !isNewlyActiveEntry) }
-            return
-        }
-
         val appInfo =
             notif.extras.getParcelable(
                 Notification.EXTRA_BUILDER_APPLICATION_INFO,
@@ -830,6 +793,15 @@
         // App Icon
         val smallIcon = sbn.notification.smallIcon
 
+        // Song name
+        var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+        if (song == null) {
+            song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
+        }
+        if (song == null) {
+            song = HybridGroupManager.resolveTitle(notif)
+        }
+
         // Explicit Indicator
         var isExplicit = false
         if (mediaFlags.isExplicitIndicatorEnabled()) {
@@ -901,7 +873,7 @@
         val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
         val appUid = appInfo?.uid ?: Process.INVALID_UID
 
-        if (isNewlyActiveEntry) {
+        if (logEvent) {
             logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
             logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
         } else if (playbackLocation != currentEntry?.playbackLocation) {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivity.kt
new file mode 100644
index 0000000..c209a00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivity.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import javax.inject.Inject
+
+/**
+ * An internal proxy activity that starts the notes role setting.
+ *
+ * This activity is introduced mainly for the error handling of the notes app lock screen shortcut
+ * picker, which only supports package + action but not extras. See
+ * [KeyguardQuickAffordanceConfig.PickerScreenState.Disabled.actionComponentName].
+ */
+class LaunchNotesRoleSettingsTrampolineActivity
+@Inject
+constructor(
+    private val controller: NoteTaskController,
+) : ComponentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val entryPoint =
+            if (intent?.action == ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE) {
+                NoteTaskEntryPoint.QUICK_AFFORDANCE
+            } else {
+                null
+            }
+        controller.startNotesRoleSetting(this, entryPoint)
+        finish()
+    }
+
+    companion object {
+        const val ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE =
+            "com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index d4052f5..7e9b346 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -31,15 +31,14 @@
 import android.content.pm.PackageManager
 import android.content.pm.ShortcutManager
 import android.graphics.drawable.Icon
-import android.os.Build
 import android.os.UserHandle
 import android.os.UserManager
-import android.util.Log
 import android.widget.Toast
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
+import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
 import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
@@ -92,10 +91,10 @@
         if (info.launchMode != NoteTaskLaunchMode.AppBubble) return
 
         if (isExpanding) {
-            logDebug { "onBubbleExpandChanged - expanding: $info" }
+            debugLog { "onBubbleExpandChanged - expanding: $info" }
             eventLogger.logNoteTaskOpened(info)
         } else {
-            logDebug { "onBubbleExpandChanged - collapsing: $info" }
+            debugLog { "onBubbleExpandChanged - collapsing: $info" }
             eventLogger.logNoteTaskClosed(info)
         }
     }
@@ -112,6 +111,43 @@
         )
     }
 
+    /** Starts the notes role setting. */
+    fun startNotesRoleSetting(activityContext: Context, entryPoint: NoteTaskEntryPoint?) {
+        val user =
+            if (entryPoint == null) {
+                userTracker.userHandle
+            } else {
+                getUserForHandlingNotesTaking(entryPoint)
+            }
+        activityContext.startActivityAsUser(
+            Intent(Intent.ACTION_MANAGE_DEFAULT_APP).apply {
+                putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES)
+            },
+            user
+        )
+    }
+
+    /**
+     * Returns the [UserHandle] of an android user that should handle the notes taking [entryPoint].
+     *
+     * On company owned personally enabled (COPE) devices, if the given [entryPoint] is in the
+     * [FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES] list, the default notes app in the work
+     * profile user will always be launched.
+     *
+     * On non managed devices or devices with other management modes, the current [UserHandle] is
+     * returned.
+     */
+    fun getUserForHandlingNotesTaking(entryPoint: NoteTaskEntryPoint): UserHandle =
+        if (
+            entryPoint in FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES &&
+                devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile
+        ) {
+            userTracker.userProfiles.firstOrNull { userManager.isManagedProfile(it.id) }?.userHandle
+                ?: userTracker.userHandle
+        } else {
+            userTracker.userHandle
+        }
+
     /**
      * Shows a note task. How the task is shown will depend on when the method is invoked.
      *
@@ -122,30 +158,13 @@
      * bubble is already opened.
      *
      * That will let users open other apps in full screen, and take contextual notes.
-     *
-     * On company owned personally enabled (COPE) devices, if the given [entryPoint] is in the
-     * [FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES] list, the default notes app in the work
-     * profile user will always be launched.
      */
     fun showNoteTask(
         entryPoint: NoteTaskEntryPoint,
     ) {
         if (!isEnabled) return
 
-        val user: UserHandle =
-            if (
-                entryPoint in FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES &&
-                    devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile
-            ) {
-                userTracker.userProfiles
-                    .firstOrNull { userManager.isManagedProfile(it.id) }
-                    ?.userHandle
-                    ?: userTracker.userHandle
-            } else {
-                userTracker.userHandle
-            }
-
-        showNoteTaskAsUser(entryPoint, user)
+        showNoteTaskAsUser(entryPoint, getUserForHandlingNotesTaking(entryPoint))
     }
 
     /** A variant of [showNoteTask] which launches note task in the given [user]. */
@@ -168,14 +187,14 @@
             isKeyguardLocked &&
                 devicePolicyManager.areKeyguardShortcutsDisabled(userId = user.identifier)
         ) {
-            logDebug { "Enterprise policy disallows launching note app when the screen is locked." }
+            debugLog { "Enterprise policy disallows launching note app when the screen is locked." }
             return
         }
 
         val info = resolver.resolveInfo(entryPoint, isKeyguardLocked, user)
 
         if (info == null) {
-            logDebug { "Default notes app isn't set" }
+            debugLog { "Default notes app isn't set" }
             showNoDefaultNotesAppToast()
             return
         }
@@ -184,7 +203,7 @@
 
         try {
             // TODO(b/266686199): We should handle when app not available. For now, we log.
-            logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" }
+            debugLog { "onShowNoteTask - start: $info on user#${user.identifier}" }
             when (info.launchMode) {
                 is NoteTaskLaunchMode.AppBubble -> {
                     val intent = createNoteTaskIntent(info)
@@ -192,7 +211,7 @@
                         Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
                     bubbles.showOrHideAppBubble(intent, user, icon)
                     // App bubble logging happens on `onBubbleExpandChanged`.
-                    logDebug { "onShowNoteTask - opened as app bubble: $info" }
+                    debugLog { "onShowNoteTask - opened as app bubble: $info" }
                 }
                 is NoteTaskLaunchMode.Activity -> {
                     if (activityManager.isInForeground(info.packageName)) {
@@ -200,20 +219,20 @@
                         val intent = createHomeIntent()
                         context.startActivityAsUser(intent, user)
                         eventLogger.logNoteTaskClosed(info)
-                        logDebug { "onShowNoteTask - closed as activity: $info" }
+                        debugLog { "onShowNoteTask - closed as activity: $info" }
                     } else {
                         val intent = createNoteTaskIntent(info)
                         context.startActivityAsUser(intent, user)
                         eventLogger.logNoteTaskOpened(info)
-                        logDebug { "onShowNoteTask - opened as activity: $info" }
+                        debugLog { "onShowNoteTask - opened as activity: $info" }
                     }
                 }
             }
-            logDebug { "onShowNoteTask - success: $info" }
+            debugLog { "onShowNoteTask - success: $info" }
         } catch (e: ActivityNotFoundException) {
-            logDebug { "onShowNoteTask - failed: $info" }
+            debugLog { "onShowNoteTask - failed: $info" }
         }
-        logDebug { "onShowNoteTask - completed: $info" }
+        debugLog { "onShowNoteTask - completed: $info" }
     }
 
     @VisibleForTesting
@@ -253,7 +272,7 @@
             PackageManager.DONT_KILL_APP,
         )
 
-        logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
+        debugLog { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
     }
 
     /**
@@ -352,11 +371,6 @@
         }
     }
 
-/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */
-private inline fun Any.logDebug(message: () -> String) {
-    if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
-}
-
 /** Creates an [Intent] which forces the current app to background by calling home. */
 private fun createHomeIntent(): Intent =
     Intent(Intent.ACTION_MAIN).apply {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 2c62ffd..4d5173a 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -45,6 +45,10 @@
     @[Binds IntoMap ClassKey(LaunchNoteTaskManagedProfileProxyActivity::class)]
     fun LaunchNoteTaskManagedProfileProxyActivity.bindNoteTaskLauncherProxyActivity(): Activity
 
+    @[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)]
+    fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity():
+        Activity
+
     companion object {
 
         @[Provides NoteTaskEnabledKey]
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index 2da5b76..444407c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -16,9 +16,12 @@
 
 package com.android.systemui.notetask.quickaffordance
 
+import android.app.role.OnRoleHoldersChangedListener
+import android.app.role.RoleManager
 import android.content.Context
 import android.hardware.input.InputSettings
 import android.os.Build
+import android.os.UserHandle
 import android.os.UserManager
 import android.util.Log
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -27,17 +30,22 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEnabledKey
-import com.android.systemui.notetask.NoteTaskEntryPoint
+import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskInfoResolver
+import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR
 import com.android.systemui.stylus.StylusManager
 import dagger.Lazy
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.channels.trySendBlocking
@@ -49,13 +57,16 @@
 class NoteTaskQuickAffordanceConfig
 @Inject
 constructor(
-    context: Context,
+    private val context: Context,
     private val controller: NoteTaskController,
+    private val noteTaskInfoResolver: NoteTaskInfoResolver,
     private val stylusManager: StylusManager,
+    private val roleManager: RoleManager,
     private val keyguardMonitor: KeyguardUpdateMonitor,
     private val userManager: UserManager,
     private val lazyRepository: Lazy<KeyguardQuickAffordanceRepository>,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
+    @Background private val backgroundExecutor: Executor,
 ) : KeyguardQuickAffordanceConfig {
 
     override val key = BuiltInKeyguardQuickAffordanceKeys.CREATE_NOTE
@@ -73,15 +84,24 @@
         val configSelectedFlow = repository.createConfigSelectedFlow(key)
         val stylusEverUsedFlow = stylusManager.createStylusEverUsedFlow(context)
         val userUnlockedFlow = userManager.createUserUnlockedFlow(keyguardMonitor)
-        combine(userUnlockedFlow, stylusEverUsedFlow, configSelectedFlow) {
+        val defaultNotesAppFlow =
+            roleManager.createNotesRoleFlow(backgroundExecutor, controller, noteTaskInfoResolver)
+        combine(userUnlockedFlow, stylusEverUsedFlow, configSelectedFlow, defaultNotesAppFlow) {
                 isUserUnlocked,
                 isStylusEverUsed,
-                isConfigSelected ->
+                isConfigSelected,
+                isDefaultNotesAppSet ->
                 logDebug { "lockScreenState:isUserUnlocked=$isUserUnlocked" }
                 logDebug { "lockScreenState:isStylusEverUsed=$isStylusEverUsed" }
                 logDebug { "lockScreenState:isConfigSelected=$isConfigSelected" }
+                logDebug { "lockScreenState:isDefaultNotesAppSet=$isDefaultNotesAppSet" }
 
-                if (isEnabled && isUserUnlocked && (isConfigSelected || isStylusEverUsed)) {
+                if (
+                    isEnabled &&
+                        isUserUnlocked &&
+                        isDefaultNotesAppSet &&
+                        (isConfigSelected || isStylusEverUsed)
+                ) {
                     val contentDescription = ContentDescription.Resource(pickerNameResourceId)
                     val icon = Icon.Resource(pickerIconResourceId, contentDescription)
                     LockScreenState.Visible(icon)
@@ -92,15 +112,34 @@
             .onEach { state -> logDebug { "lockScreenState=$state" } }
     }
 
-    override suspend fun getPickerScreenState() =
-        if (isEnabled) {
-            PickerScreenState.Default()
-        } else {
-            PickerScreenState.UnavailableOnDevice
+    override suspend fun getPickerScreenState(): PickerScreenState {
+        val isDefaultNotesAppSet =
+            noteTaskInfoResolver.resolveInfo(
+                QUICK_AFFORDANCE,
+                user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
+            ) != null
+        return when {
+            isEnabled && isDefaultNotesAppSet -> PickerScreenState.Default()
+            isEnabled -> {
+                PickerScreenState.Disabled(
+                    listOf(
+                        context.getString(
+                            R.string.keyguard_affordance_enablement_dialog_notes_app_instruction
+                        )
+                    ),
+                    context.getString(
+                        R.string.keyguard_affordance_enablement_dialog_notes_app_action
+                    ),
+                    "${context.packageName}$COMPONENT_NAME_SEPARATOR" +
+                        "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE",
+                )
+            }
+            else -> PickerScreenState.UnavailableOnDevice
         }
+    }
 
     override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
-        controller.showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        controller.showNoteTask(entryPoint = QUICK_AFFORDANCE)
         return OnTriggeredResult.Handled
     }
 }
@@ -129,6 +168,27 @@
     awaitClose { unregisterCallback(callback) }
 }
 
+private fun RoleManager.createNotesRoleFlow(
+    executor: Executor,
+    noteTaskController: NoteTaskController,
+    noteTaskInfoResolver: NoteTaskInfoResolver,
+) = callbackFlow {
+    fun isDefaultNotesAppSetForUser() =
+        noteTaskInfoResolver.resolveInfo(
+            QUICK_AFFORDANCE,
+            user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
+        ) != null
+
+    trySendBlocking(isDefaultNotesAppSetForUser())
+    val callback = OnRoleHoldersChangedListener { roleName, _ ->
+        if (roleName == RoleManager.ROLE_NOTES) {
+            trySendBlocking(isDefaultNotesAppSetForUser())
+        }
+    }
+    addOnRoleHoldersChangedListenerAsUser(executor, callback, UserHandle.ALL)
+    awaitClose { removeOnRoleHoldersChangedListenerAsUser(callback, UserHandle.ALL) }
+}
+
 private fun KeyguardQuickAffordanceRepository.createConfigSelectedFlow(key: String) =
     selections.map { selected ->
         selected.values.flatten().any { selectedConfig -> selectedConfig.key == key }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 0f38d32..8ca13b9 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -18,12 +18,11 @@
 
 import android.content.Context
 import android.content.Intent
-import android.os.Build
 import android.os.Bundle
 import android.os.UserHandle
 import android.os.UserManager
-import android.util.Log
 import androidx.activity.ComponentActivity
+import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
 import com.android.systemui.settings.UserTracker
@@ -68,7 +67,7 @@
         val mainUser: UserHandle? = userManager.mainUser
         if (userManager.isManagedProfile) {
             if (mainUser == null) {
-                logDebug { "Can't find the main user. Skipping the notes app launch." }
+                debugLog { "Can't find the main user. Skipping the notes app launch." }
             } else {
                 controller.startNoteTaskProxyActivityForUser(mainUser)
             }
@@ -89,8 +88,3 @@
         }
     }
 }
-
-/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */
-private inline fun Any.logDebug(message: () -> String) {
-    if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
-}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 79167f2..166ba9f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -15,6 +15,7 @@
 package com.android.systemui.privacy
 
 import android.content.Context
+import android.content.res.Configuration
 import android.util.AttributeSet
 import android.view.Gravity.CENTER_VERTICAL
 import android.view.Gravity.END
@@ -102,6 +103,11 @@
                 R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
     }
 
+    override fun onConfigurationChanged(newConfig: Configuration?) {
+        super.onConfigurationChanged(newConfig)
+        updateResources()
+    }
+
     private fun updateResources() {
         iconMargin = context.resources
                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
@@ -110,8 +116,11 @@
         iconColor =
                 Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
 
+        val height = context.resources
+                .getDimensionPixelSize(R.dimen.ongoing_appops_chip_height)
         val padding = context.resources
                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
+        iconsContainer.layoutParams.height = height
         iconsContainer.setPaddingRelative(padding, 0, padding, 0)
         iconsContainer.background = context.getDrawable(R.drawable.statusbar_privacy_chip_bg)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index b7f9f6b..1afc885 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -38,7 +38,9 @@
  */
 public class QSContainerImpl extends FrameLayout implements Dumpable {
 
+    private int mFancyClippingLeftInset;
     private int mFancyClippingTop;
+    private int mFancyClippingRightInset;
     private int mFancyClippingBottom;
     private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
     private  final Path mFancyClippingPath = new Path();
@@ -53,6 +55,7 @@
     private boolean mQsDisabled;
     private int mContentHorizontalPadding = -1;
     private boolean mClippingEnabled;
+    private boolean mIsFullWidth;
 
     public QSContainerImpl(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -237,7 +240,8 @@
     /**
      * Clip QS bottom using a concave shape.
      */
-    public void setFancyClipping(int top, int bottom, int radius, boolean enabled) {
+    public void setFancyClipping(int leftInset, int top, int rightInset, int bottom, int radius,
+            boolean enabled, boolean fullWidth) {
         boolean updatePath = false;
         if (mFancyClippingRadii[0] != radius) {
             mFancyClippingRadii[0] = radius;
@@ -246,10 +250,18 @@
             mFancyClippingRadii[3] = radius;
             updatePath = true;
         }
+        if (mFancyClippingLeftInset != leftInset) {
+            mFancyClippingLeftInset = leftInset;
+            updatePath = true;
+        }
         if (mFancyClippingTop != top) {
             mFancyClippingTop = top;
             updatePath = true;
         }
+        if (mFancyClippingRightInset != rightInset) {
+            mFancyClippingRightInset = rightInset;
+            updatePath = true;
+        }
         if (mFancyClippingBottom != bottom) {
             mFancyClippingBottom = bottom;
             updatePath = true;
@@ -258,6 +270,10 @@
             mClippingEnabled = enabled;
             updatePath = true;
         }
+        if (mIsFullWidth != fullWidth) {
+            mIsFullWidth = fullWidth;
+            updatePath = true;
+        }
 
         if (updatePath) {
             updateClippingPath();
@@ -281,15 +297,21 @@
             return;
         }
 
-        mFancyClippingPath.addRoundRect(0, mFancyClippingTop, getWidth(),
+        int clippingLeft = mIsFullWidth ? -mFancyClippingLeftInset : 0;
+        int clippingRight = mIsFullWidth ? getWidth() + mFancyClippingRightInset : getWidth();
+        mFancyClippingPath.addRoundRect(clippingLeft, mFancyClippingTop, clippingRight,
                 mFancyClippingBottom, mFancyClippingRadii, Path.Direction.CW);
         invalidate();
     }
 
     @Override
     public void dump(PrintWriter pw, String[] args) {
-        pw.println(getClass().getSimpleName() + " updateClippingPath: top("
-                + mFancyClippingTop + ") bottom(" + mFancyClippingBottom  + ") mClippingEnabled("
-                + mClippingEnabled + ")");
+        pw.println(getClass().getSimpleName() + " updateClippingPath: "
+                + "leftInset(" + mFancyClippingLeftInset + ") "
+                + "top(" + mFancyClippingTop + ") "
+                + "rightInset(" + mFancyClippingRightInset + ") "
+                + "bottom(" + mFancyClippingBottom  + ") "
+                + "mClippingEnabled(" + mClippingEnabled + ") "
+                + "mIsFullWidth(" + mIsFullWidth + ")");
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index d806afa..fd3f701 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -18,9 +18,10 @@
 
 import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
 import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
-import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
+import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.res.Configuration;
@@ -395,9 +396,11 @@
     }
 
     @Override
-    public void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible) {
+    public void setFancyClipping(int leftInset, int top, int rightInset, int bottom,
+            int cornerRadius, boolean visible, boolean fullWidth) {
         if (getView() instanceof QSContainerImpl) {
-            ((QSContainerImpl) getView()).setFancyClipping(top, bottom, cornerRadius, visible);
+            ((QSContainerImpl) getView()).setFancyClipping(leftInset, top, rightInset, bottom,
+                    cornerRadius, visible, fullWidth);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 7f7f8ad6..2d9f7dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -68,6 +68,7 @@
     private final SensorPrivacyManager mPrivacyManager;
     private final BatteryController mBatteryController;
     private final SettingObserver mSetting;
+    private final boolean mAllowRotationResolver;
 
     @Inject
     public RotationLockTile(
@@ -105,6 +106,8 @@
             }
         };
         mBatteryController.observe(getLifecycle(), this);
+        mAllowRotationResolver = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_allowRotationResolver);
     }
 
     @Override
@@ -145,7 +148,7 @@
 
         final boolean powerSave = mBatteryController.isPowerSave();
         final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(CAMERA);
-        final boolean cameraRotation =
+        final boolean cameraRotation = mAllowRotationResolver &&
                 !powerSave && !cameraLocked && hasSufficientPermission(mContext)
                         && mController.isCameraRotationEnabled();
         state.value = !rotationLocked;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 69008cc..84f358c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -33,6 +33,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
@@ -60,11 +61,10 @@
     public static final int REQUEST_CODE = 2;
 
     private static final int USER_ID_NOT_SPECIFIED = -1;
-    private static final int NOTIFICATION_RECORDING_ID = 4274;
-    private static final int NOTIFICATION_PROCESSING_ID = 4275;
-    private static final int NOTIFICATION_VIEW_ID = 4273;
+    private static final int NOTIF_BASE_ID = 4273;
     private static final String TAG = "RecordingService";
     private static final String CHANNEL_ID = "screen_record";
+    private static final String GROUP_KEY = "screen_record_saved";
     private static final String EXTRA_RESULT_CODE = "extra_resultCode";
     private static final String EXTRA_PATH = "extra_path";
     private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
@@ -89,6 +89,7 @@
     private final UiEventLogger mUiEventLogger;
     private final NotificationManager mNotificationManager;
     private final UserContextProvider mUserContextTracker;
+    private int mNotificationId = NOTIF_BASE_ID;
 
     @Inject
     public RecordingService(RecordingController controller, @LongRunning Executor executor,
@@ -134,14 +135,23 @@
         }
         String action = intent.getAction();
         Log.d(TAG, "onStartCommand " + action);
+        NotificationChannel channel = new NotificationChannel(
+                CHANNEL_ID,
+                getString(R.string.screenrecord_title),
+                NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setDescription(getString(R.string.screenrecord_channel_description));
+        channel.enableVibration(true);
+        mNotificationManager.createNotificationChannel(channel);
 
         int currentUserId = mUserContextTracker.getUserContext().getUserId();
         UserHandle currentUser = new UserHandle(currentUserId);
         switch (action) {
             case ACTION_START:
+                // Get a unique ID for this recording's notifications
+                mNotificationId = NOTIF_BASE_ID + (int) SystemClock.uptimeMillis();
                 mAudioSource = ScreenRecordingAudioSource
                         .values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)];
-                Log.d(TAG, "recording with audio source" + mAudioSource);
+                Log.d(TAG, "recording with audio source " + mAudioSource);
                 mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
                 MediaProjectionCaptureTarget captureTarget =
                         intent.getParcelableExtra(EXTRA_CAPTURE_TARGET,
@@ -169,7 +179,7 @@
                 } else {
                     updateState(false);
                     createErrorNotification();
-                    stopForeground(true);
+                    stopForeground(STOP_FOREGROUND_DETACH);
                     stopSelf();
                     return Service.START_NOT_STICKY;
                 }
@@ -200,7 +210,7 @@
                     startActivity(Intent.createChooser(shareIntent, shareLabel)
                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
                     // Remove notification
-                    mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
+                    mNotificationManager.cancelAsUser(null, mNotificationId, currentUser);
                     return false;
                 }, false, false);
 
@@ -260,14 +270,6 @@
     @VisibleForTesting
     protected void createErrorNotification() {
         Resources res = getResources();
-        NotificationChannel channel = new NotificationChannel(
-                CHANNEL_ID,
-                getString(R.string.screenrecord_title),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        channel.setDescription(getString(R.string.screenrecord_channel_description));
-        channel.enableVibration(true);
-        mNotificationManager.createNotificationChannel(channel);
-
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 res.getString(R.string.screenrecord_title));
@@ -277,7 +279,7 @@
                 .setSmallIcon(R.drawable.ic_screenrecord)
                 .setContentTitle(notificationTitle)
                 .addExtras(extras);
-        startForeground(NOTIFICATION_RECORDING_ID, builder.build());
+        startForeground(mNotificationId, builder.build());
     }
 
     @VisibleForTesting
@@ -288,14 +290,6 @@
     @VisibleForTesting
     protected void createRecordingNotification() {
         Resources res = getResources();
-        NotificationChannel channel = new NotificationChannel(
-                CHANNEL_ID,
-                getString(R.string.screenrecord_title),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        channel.setDescription(getString(R.string.screenrecord_channel_description));
-        channel.enableVibration(true);
-        mNotificationManager.createNotificationChannel(channel);
-
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 res.getString(R.string.screenrecord_title));
@@ -323,7 +317,7 @@
                 .setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
                 .addAction(stopAction)
                 .addExtras(extras);
-        startForeground(NOTIFICATION_RECORDING_ID, builder.build());
+        startForeground(mNotificationId, builder.build());
     }
 
     @VisibleForTesting
@@ -337,11 +331,12 @@
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 res.getString(R.string.screenrecord_title));
 
-        Notification.Builder builder = new Notification.Builder(getApplicationContext(), CHANNEL_ID)
+        Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
                 .setContentTitle(notificationTitle)
                 .setContentText(
                         getResources().getString(R.string.screenrecord_background_processing_label))
                 .setSmallIcon(R.drawable.ic_screenrecord)
+                .setGroup(GROUP_KEY)
                 .addExtras(extras);
         return builder.build();
     }
@@ -378,6 +373,7 @@
                         PendingIntent.FLAG_IMMUTABLE))
                 .addAction(shareAction)
                 .setAutoCancel(true)
+                .setGroup(GROUP_KEY)
                 .addExtras(extras);
 
         // Add thumbnail if available
@@ -391,6 +387,24 @@
         return builder.build();
     }
 
+    /**
+     * Adds a group notification so that save notifications from multiple recordings are
+     * grouped together, and the foreground service recording notification is not
+     */
+    private void postGroupNotification(UserHandle currentUser) {
+        Bundle extras = new Bundle();
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
+                getResources().getString(R.string.screenrecord_title));
+        Notification groupNotif = new Notification.Builder(this, CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_screenrecord)
+                .setContentTitle(getResources().getString(R.string.screenrecord_save_title))
+                .setGroup(GROUP_KEY)
+                .setGroupSummary(true)
+                .setExtras(extras)
+                .build();
+        mNotificationManager.notifyAsUser(TAG, NOTIF_BASE_ID, groupNotif, currentUser);
+    }
+
     private void stopService() {
         stopService(USER_ID_NOT_SPECIFIED);
     }
@@ -423,27 +437,26 @@
             Log.e(TAG, "stopRecording called, but recorder was null");
         }
         updateState(false);
+        stopForeground(STOP_FOREGROUND_DETACH);
         stopSelf();
     }
 
     private void saveRecording(int userId) {
         UserHandle currentUser = new UserHandle(userId);
-        mNotificationManager.notifyAsUser(null, NOTIFICATION_PROCESSING_ID,
+        mNotificationManager.notifyAsUser(null, mNotificationId,
                 createProcessingNotification(), currentUser);
 
         mLongExecutor.execute(() -> {
             try {
                 Log.d(TAG, "saving recording");
                 Notification notification = createSaveNotification(getRecorder().save());
-                if (!mController.isRecording()) {
-                    mNotificationManager.notifyAsUser(null, NOTIFICATION_VIEW_ID, notification,
-                            currentUser);
-                }
+                postGroupNotification(currentUser);
+                mNotificationManager.notifyAsUser(null, mNotificationId,  notification,
+                        currentUser);
             } catch (IOException e) {
                 Log.e(TAG, "Error saving screen recording: " + e.getMessage());
                 showErrorToast(R.string.screenrecord_delete_error);
-            } finally {
-                mNotificationManager.cancelAsUser(null, NOTIFICATION_PROCESSING_ID, currentUser);
+                mNotificationManager.cancelAsUser(null, mNotificationId, currentUser);
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 5117915..137a99ef 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4474,7 +4474,7 @@
         mDisplayTopInset = combinedInsets.top;
         mDisplayRightInset = combinedInsets.right;
         mDisplayLeftInset = combinedInsets.left;
-        mQsController.setDisplayInsets(mDisplayRightInset, mDisplayLeftInset);
+        mQsController.setDisplayInsets(mDisplayLeftInset, mDisplayRightInset);
 
         mNavigationBarBottomHeight = insets.getStableInsetBottom();
         updateMaxHeadsUpTranslation();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 92c9a80..a1fa8fb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -887,7 +887,9 @@
     }
 
     void setOverScrollAmount(int overExpansion) {
-        mQs.setOverScrollAmount(overExpansion);
+        if (mQs != null) {
+            mQs.setOverScrollAmount(overExpansion);
+        }
     }
 
     private void setOverScrolling(boolean overscrolling) {
@@ -1230,10 +1232,13 @@
             mVisible = qsVisible;
             mQs.setQsVisible(qsVisible);
             mQs.setFancyClipping(
+                    mDisplayLeftInset,
                     clipTop,
+                    mDisplayRightInset,
                     clipBottom,
                     radius,
-                    qsVisible && !mSplitShadeEnabled);
+                    qsVisible && !mSplitShadeEnabled,
+                    mIsFullWidth);
 
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index 07b6869..b6970ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -20,6 +20,7 @@
 import android.content.res.Resources
 import android.os.SystemProperties
 import android.os.Trace
+import android.os.Trace.TRACE_TAG_APP
 import android.util.IndentingPrintWriter
 import android.util.MathUtils
 import android.view.CrossWindowBlurListeners
@@ -43,8 +44,8 @@
 ) : Dumpable {
     val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius)
     val maxBlurRadius = resources.getDimensionPixelSize(R.dimen.max_window_blur_radius)
-    private val traceCookie = System.identityHashCode(this)
     private var lastAppliedBlur = 0
+    private var earlyWakeupEnabled = false
 
     init {
         dumpManager.registerDumpable(javaClass.name, this)
@@ -72,6 +73,26 @@
     }
 
     /**
+     * This method should be called before [applyBlur] so that, if needed, we can set the
+     * early-wakeup flag in SurfaceFlinger.
+     */
+    fun prepareBlur(viewRootImpl: ViewRootImpl?, radius: Int) {
+        if (viewRootImpl == null || !viewRootImpl.surfaceControl.isValid ||
+            !supportsBlursOnWindows() || earlyWakeupEnabled
+        ) {
+            return
+        }
+        if (lastAppliedBlur == 0 && radius != 0) {
+            Trace.asyncTraceForTrackBegin(TRACE_TAG_APP, TRACK_NAME, EARLY_WAKEUP_SLICE_NAME, 0)
+            earlyWakeupEnabled = true
+            createTransaction().use {
+                it.setEarlyWakeupStart()
+                it.apply()
+            }
+        }
+    }
+
+    /**
      * Applies background blurs to a {@link ViewRootImpl}.
      *
      * @param viewRootImpl The window root.
@@ -85,14 +106,20 @@
         createTransaction().use {
             if (supportsBlursOnWindows()) {
                 it.setBackgroundBlurRadius(viewRootImpl.surfaceControl, radius)
-                if (lastAppliedBlur == 0 && radius != 0) {
-                    Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME,
-                            EARLY_WAKEUP_SLICE_NAME, traceCookie)
+                if (!earlyWakeupEnabled && lastAppliedBlur == 0 && radius != 0) {
+                    Trace.asyncTraceForTrackBegin(
+                        TRACE_TAG_APP,
+                        TRACK_NAME,
+                        EARLY_WAKEUP_SLICE_NAME,
+                        0
+                    )
                     it.setEarlyWakeupStart()
+                    earlyWakeupEnabled = true
                 }
-                if (lastAppliedBlur != 0 && radius == 0) {
+                if (earlyWakeupEnabled && lastAppliedBlur != 0 && radius == 0) {
                     it.setEarlyWakeupEnd()
-                    Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, traceCookie)
+                    Trace.asyncTraceForTrackEnd(TRACE_TAG_APP, TRACK_NAME, 0)
+                    earlyWakeupEnabled = false
                 }
                 lastAppliedBlur = radius
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 8dc7842..a3bd247 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -189,12 +189,7 @@
             scheduleUpdate()
         }
 
-    /**
-     * Callback that updates the window blur value and is called only once per frame.
-     */
-    @VisibleForTesting
-    val updateBlurCallback = Choreographer.FrameCallback {
-        updateScheduled = false
+    private fun computeBlurAndZoomOut(): Pair<Int, Float> {
         val animationRadius = MathUtils.constrain(shadeAnimation.radius,
                 blurUtils.minBlurRadius.toFloat(), blurUtils.maxBlurRadius.toFloat())
         val expansionRadius = blurUtils.blurRadiusOfRatio(
@@ -232,6 +227,16 @@
         // Brightness slider removes blur, but doesn't affect zooms
         blur = (blur * (1f - brightnessMirrorSpring.ratio)).toInt()
 
+        return Pair(blur, zoomOut)
+    }
+
+    /**
+     * Callback that updates the window blur value and is called only once per frame.
+     */
+    @VisibleForTesting
+    val updateBlurCallback = Choreographer.FrameCallback {
+        updateScheduled = false
+        val (blur, zoomOut) = computeBlurAndZoomOut()
         val opaque = scrimsVisible && !blursDisabledForAppLaunch
         Trace.traceCounter(Trace.TRACE_TAG_APP, "shade_blur_radius", blur)
         blurUtils.applyBlur(root.viewRootImpl, blur, opaque)
@@ -441,6 +446,8 @@
             return
         }
         updateScheduled = true
+        val (blur, _) = computeBlurAndZoomOut()
+        blurUtils.prepareBlur(root.viewRootImpl, blur)
         choreographer.postFrameCallback(updateBlurCallback)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 7755003..129c859 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -26,6 +26,7 @@
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -75,9 +76,9 @@
      */
     private static final float DARK_ALPHA_BOOST = 0.67f;
     /**
-     * Status icons are currently drawn with the intention of being 17dp tall, but we
-     * want to scale them (in a way that doesn't require an asset dump) down 2dp. So
-     * 17dp * (15 / 17) = 15dp, the new height. After the first call to {@link #reloadDimens} all
+     * Status icons are currently drawn with the intention of being 17sp tall, but we
+     * want to scale them (in a way that doesn't require an asset dump) down 2sp. So
+     * 17sp * (15 / 17) = 15sp, the new height. After the first call to {@link #reloadDimens} all
      * values will be in px.
      */
     private float mSystemIconDesiredHeight = 15f;
@@ -144,7 +145,7 @@
     private String mNumberText;
     private StatusBarNotification mNotification;
     private final boolean mBlocked;
-    private int mDensity;
+    private Configuration mConfiguration;
     private boolean mNightMode;
     private float mIconScale = 1.0f;
     private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -198,9 +199,8 @@
         mNumberPain.setAntiAlias(true);
         setNotification(sbn);
         setScaleType(ScaleType.CENTER);
-        mDensity = context.getResources().getDisplayMetrics().densityDpi;
-        Configuration configuration = context.getResources().getConfiguration();
-        mNightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+        mConfiguration = new Configuration(context.getResources().getConfiguration());
+        mNightMode = (mConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
                 == Configuration.UI_MODE_NIGHT_YES;
         initializeDecorColor();
         reloadDimens();
@@ -214,7 +214,7 @@
         mAlwaysScaleIcon = true;
         reloadDimens();
         maybeUpdateIconScaleDimens();
-        mDensity = context.getResources().getDisplayMetrics().densityDpi;
+        mConfiguration = new Configuration(context.getResources().getConfiguration());
     }
 
     /** Should always be preceded by {@link #reloadDimens()} */
@@ -231,12 +231,17 @@
     private void updateIconScaleForNotifications() {
         final float imageBounds = mIncreasedSize ?
                 mStatusBarIconDrawingSizeIncreased : mStatusBarIconDrawingSize;
-        final int outerBounds = mStatusBarIconSize;
-        mIconScale = imageBounds / (float)outerBounds;
+        float iconHeight = getIconHeight();
+        if (iconHeight != 0) {
+            mIconScale = imageBounds / iconHeight;
+        } else {
+            final int outerBounds = mStatusBarIconSize;
+            mIconScale = imageBounds / (float) outerBounds;
+        }
         updatePivot();
     }
 
-    // Makes sure that all icons are scaled to the same height (15dp). If we cannot get a height
+    // Makes sure that all icons are scaled to the same height (15sp). If we cannot get a height
     // for the icon, it uses the default SCALE (15f / 17f) which is the old behavior
     private void updateIconScaleForSystemIcons() {
         float iconHeight = getIconHeight();
@@ -267,12 +272,10 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        int density = newConfig.densityDpi;
-        if (density != mDensity) {
-            mDensity = density;
-            reloadDimens();
-            updateDrawable();
-            maybeUpdateIconScaleDimens();
+        final int configDiff = newConfig.diff(mConfiguration);
+        mConfiguration.setTo(newConfig);
+        if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
+            updateIconDimens();
         }
         boolean nightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
                 == Configuration.UI_MODE_NIGHT_YES;
@@ -282,6 +285,15 @@
         }
     }
 
+    /**
+     * Update the icon dimens and drawable with current resources
+     */
+    public void updateIconDimens() {
+        reloadDimens();
+        updateDrawable();
+        maybeUpdateIconScaleDimens();
+    }
+
     private void reloadDimens() {
         boolean applyRadius = mDotRadius == mStaticDotRadius;
         Resources res = getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 006a029d..b9a12e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -306,7 +306,7 @@
     public void applyIconStates() {
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
-            ViewState childState = mIconStates.get(child);
+            IconState childState = mIconStates.get(child);
             if (childState != null) {
                 childState.applyToView(child);
             }
@@ -339,6 +339,7 @@
             }
         }
         if (child instanceof StatusBarIconView) {
+            ((StatusBarIconView) child).updateIconDimens();
             ((StatusBarIconView) child).setDozing(mDozing, false, 0);
         }
     }
@@ -447,9 +448,14 @@
     @VisibleForTesting
     boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd,
             float iconSize) {
-        // Layout end, as used here, does not include padding end.
-        final float overflowX = isLastChild ? layoutEnd : layoutEnd - iconSize;
-        return translationX >= overflowX;
+        if (isLastChild) {
+            return translationX + iconSize > layoutEnd;
+        } else {
+            // If the child is not the last child, we need to ensure that we have room for the next
+            // icon and the dot. The dot could be as large as an icon, so verify that we have room
+            // for 2 icons.
+            return translationX + iconSize * 2f > layoutEnd;
+        }
     }
 
     /**
@@ -489,10 +495,7 @@
             // First icon to overflow.
             if (firstOverflowIndex == -1 && isOverflowing) {
                 firstOverflowIndex = i;
-                mVisualOverflowStart = layoutEnd - mIconSize;
-                if (forceOverflow || mIsStaticLayout) {
-                    mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
-                }
+                mVisualOverflowStart = translationX;
             }
             final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
                     ? ((StatusBarIconView) view).getIconScaleIncreased()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index a8a834f..678873c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -203,8 +203,7 @@
 
         @Override
         protected LayoutParams onCreateLayoutParams() {
-            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
-                    ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
+            LinearLayout.LayoutParams lp = super.onCreateLayoutParams();
             lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
             return lp;
         }
@@ -370,7 +369,7 @@
         private final MobileIconsViewModel mMobileIconsViewModel;
 
         protected final Context mContext;
-        protected final int mIconSize;
+        protected int mIconSize;
         // Whether or not these icons show up in dumpsys
         protected boolean mShouldLog = false;
         private StatusBarIconController mController;
@@ -395,10 +394,10 @@
             mStatusBarPipelineFlags = statusBarPipelineFlags;
             mMobileContextProvider = mobileContextProvider;
             mContext = group.getContext();
-            mIconSize = mContext.getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.status_bar_icon_size);
             mLocation = location;
 
+            reloadDimens();
+
             if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
                 // This starts the flow for the new pipeline, and will notify us of changes if
                 // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
@@ -609,13 +608,9 @@
             mGroup.removeAllViews();
         }
 
-        protected void onDensityOrFontScaleChanged() {
-            for (int i = 0; i < mGroup.getChildCount(); i++) {
-                View child = mGroup.getChildAt(i);
-                LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
-                        ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
-                child.setLayoutParams(lp);
-            }
+        protected void reloadDimens() {
+            mIconSize = mContext.getResources().getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_icon_size);
         }
 
         private void setHeightAndCenter(ImageView imageView, int height) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 3a18423..80d5651 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -109,6 +109,7 @@
         }
 
         group.setController(this);
+        group.reloadDimens();
         mIconGroups.add(group);
         List<Slot> allSlots = mStatusBarIconList.getSlots();
         for (int i = 0; i < allSlots.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 26c1767..ddbfd43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -22,6 +22,8 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -72,13 +74,16 @@
     // Any ignored icon will never be added as a child
     private ArrayList<String> mIgnoredSlots = new ArrayList<>();
 
+    private Configuration mConfiguration;
+
     public StatusIconContainer(Context context) {
         this(context, null);
     }
 
     public StatusIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
-        initDimens();
+        mConfiguration = new Configuration(context.getResources().getConfiguration());
+        reloadDimens();
         setWillNotDraw(!DEBUG_OVERFLOW);
     }
 
@@ -95,7 +100,7 @@
         return mShouldRestrictIcons;
     }
 
-    private void initDimens() {
+    private void reloadDimens() {
         // This is the same value that StatusBarIconView uses
         mIconDotFrameWidth = getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.status_bar_icon_size);
@@ -211,6 +216,16 @@
         child.setTag(R.id.status_bar_view_state_tag, null);
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        final int configDiff = newConfig.diff(mConfiguration);
+        mConfiguration.setTo(newConfig);
+        if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
+            reloadDimens();
+        }
+    }
+
     /**
      * Add a name of an icon slot to be ignored. It will not show up nor be measured
      * @param slotName name of the icon as it exists in
@@ -348,13 +363,17 @@
         int totalVisible = mLayoutStates.size();
         int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
 
-        mUnderflowStart = 0;
+        // Init mUnderflowStart value with the offset to let the dot be placed next to battery icon.
+        // It to prevent if the underflow happens at rightest(totalVisible - 1) child then break the
+        // for loop with mUnderflowStart staying 0(initial value), causing the dot be placed at the
+        // leftest side.
+        mUnderflowStart = (int) Math.max(contentStart, width - getPaddingEnd() - mUnderflowWidth);
         int visible = 0;
         int firstUnderflowIndex = -1;
         for (int i = totalVisible - 1; i >= 0; i--) {
             StatusIconState state = mLayoutStates.get(i);
             // Allow room for underflow if we found we need it in onMeasure
-            if (mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth))
+            if ((mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth)))
                     || (mShouldRestrictIcons && (visible >= maxVisible))) {
                 firstUnderflowIndex = i;
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 412b315..27aaa68 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -22,7 +22,6 @@
 import android.hardware.BatteryState
 import android.hardware.input.InputManager
 import android.hardware.input.InputSettings
-import android.os.Build
 import android.os.Handler
 import android.util.ArrayMap
 import android.util.Log
@@ -35,6 +34,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.shared.hardware.hasInputDevice
 import com.android.systemui.shared.hardware.isInternalStylusSource
 import java.util.concurrent.CopyOnWriteArrayList
@@ -81,7 +81,7 @@
     fun startListener() {
         handler.post {
             if (hasStarted) return@post
-            logDebug { "Listener has started." }
+            debugLog { "Listener has started." }
 
             hasStarted = true
             isInUsiSession =
@@ -109,7 +109,7 @@
 
         val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
         if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
-        logDebug {
+        debugLog {
             "Stylus InputDevice added: $deviceId ${device.name}, " +
                 "External: ${device.isExternal}"
         }
@@ -134,7 +134,7 @@
 
         val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
         if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
-        logDebug { "Stylus InputDevice changed: $deviceId ${device.name}" }
+        debugLog { "Stylus InputDevice changed: $deviceId ${device.name}" }
 
         val currAddress: String? = device.bluetoothAddress
         val prevAddress: String? = inputDeviceAddressMap[deviceId]
@@ -155,7 +155,7 @@
         if (!hasStarted) return
 
         if (!inputDeviceAddressMap.contains(deviceId)) return
-        logDebug { "Stylus InputDevice removed: $deviceId" }
+        debugLog { "Stylus InputDevice removed: $deviceId" }
 
         unregisterBatteryListener(deviceId)
 
@@ -180,7 +180,7 @@
 
             val isCharging = String(value) == "true"
 
-            logDebug {
+            debugLog {
                 "Charging state metadata changed for device $inputDeviceId " +
                     "${device.address}: $isCharging"
             }
@@ -199,7 +199,7 @@
         handler.post {
             if (!hasStarted) return@post
 
-            logDebug {
+            debugLog {
                 "Battery state changed for $deviceId. " +
                     "batteryState present: ${batteryState.isPresent}, " +
                     "capacity: ${batteryState.capacity}"
@@ -247,7 +247,7 @@
         if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return
         if (InputSettings.isStylusEverUsed(context)) return
 
-        logDebug { "Stylus used for the first time." }
+        debugLog { "Stylus used for the first time." }
         InputSettings.setStylusEverUsed(context, true)
         executeStylusCallbacks { cb -> cb.onStylusFirstUsed() }
     }
@@ -264,7 +264,7 @@
         val hasBtConnection = if (inputDeviceBtSessionIdMap.isEmpty()) 0 else 1
 
         if (batteryStateValid && usiSessionId == null) {
-            logDebug { "USI battery newly present, entering new USI session: $deviceId" }
+            debugLog { "USI battery newly present, entering new USI session: $deviceId" }
             usiSessionId = instanceIdSequence.newInstanceId()
             uiEventLogger.logWithInstanceIdAndPosition(
                 StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
@@ -274,7 +274,7 @@
                 hasBtConnection,
             )
         } else if (!batteryStateValid && usiSessionId != null) {
-            logDebug { "USI battery newly absent, exiting USI session: $deviceId" }
+            debugLog { "USI battery newly absent, exiting USI session: $deviceId" }
             uiEventLogger.logWithInstanceIdAndPosition(
                 StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED,
                 0,
@@ -291,7 +291,7 @@
         btAddress: String,
         btConnected: Boolean
     ) {
-        logDebug {
+        debugLog {
             "Bluetooth stylus ${if (btConnected) "connected" else "disconnected"}:" +
                 " $deviceId $btAddress"
         }
@@ -386,9 +386,3 @@
         val TAG = StylusManager::class.simpleName.orEmpty()
     }
 }
-
-private inline fun logDebug(message: () -> String) {
-    if (Build.IS_DEBUGGABLE) {
-        Log.d(StylusManager.TAG, message())
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index 21b0efa..6eddd9e 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -26,7 +26,6 @@
 import android.content.IntentFilter
 import android.hardware.BatteryState
 import android.hardware.input.InputManager
-import android.os.Build
 import android.os.Bundle
 import android.os.Handler
 import android.os.UserHandle
@@ -40,6 +39,7 @@
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.shared.hardware.hasInputDevice
 import com.android.systemui.shared.hardware.isAnyStylusSource
 import com.android.systemui.util.NotificationChannels
@@ -110,7 +110,7 @@
 
             inputDeviceId = deviceId
             batteryCapacity = batteryState.capacity
-            logDebug {
+            debugLog {
                 "Updating notification battery state to $batteryCapacity " +
                     "for InputDevice $deviceId."
             }
@@ -130,14 +130,14 @@
         handler.post updateSuppressed@{
             if (suppressed == suppress) return@updateSuppressed
 
-            logDebug { "Updating notification suppression to $suppress." }
+            debugLog { "Updating notification suppression to $suppress." }
             suppressed = suppress
             refresh()
         }
     }
 
     private fun hideNotification() {
-        logDebug { "Cancelling USI low battery notification." }
+        debugLog { "Cancelling USI low battery notification." }
         instanceId = null
         notificationManager.cancel(USI_NOTIFICATION_ID)
     }
@@ -160,7 +160,7 @@
                 .setAutoCancel(true)
                 .build()
 
-        logDebug { "Show or update USI low battery notification at $batteryCapacity." }
+        debugLog { "Show or update USI low battery notification at $batteryCapacity." }
         logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN)
         notificationManager.notify(USI_NOTIFICATION_ID, notification)
     }
@@ -188,12 +188,12 @@
             override fun onReceive(context: Context, intent: Intent) {
                 when (intent.action) {
                     ACTION_DISMISSED_LOW_BATTERY -> {
-                        logDebug { "USI low battery notification dismissed." }
+                        debugLog { "USI low battery notification dismissed." }
                         logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED)
                         updateSuppression(true)
                     }
                     ACTION_CLICKED_LOW_BATTERY -> {
-                        logDebug { "USI low battery notification clicked." }
+                        debugLog { "USI low battery notification clicked." }
                         logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED)
                         updateSuppression(true)
                         if (inputDeviceId == null) return
@@ -263,9 +263,3 @@
         @VisibleForTesting const val KEY_SETTINGS_FRAGMENT_ARGS = ":settings:show_fragment_args"
     }
 }
-
-private inline fun logDebug(message: () -> String) {
-    if (Build.IS_DEBUGGABLE) {
-        Log.d(StylusUsiPowerUI.TAG, message())
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index b3e7cb0..e60f9b6 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -33,6 +33,7 @@
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
+import android.view.Display;
 import android.view.KeyEvent;
 
 import androidx.annotation.NonNull;
@@ -347,12 +348,16 @@
     void initDesktopMode(DesktopMode desktopMode) {
         desktopMode.addVisibleTasksListener(
                 new DesktopModeTaskRepository.VisibleTasksListener() {
-            @Override
-            public void onVisibilityChanged(boolean hasFreeformTasks) {
-                mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, hasFreeformTasks)
-                        .commitUpdate(mDisplayTracker.getDefaultDisplayId());
-            }
-        }, mSysUiMainExecutor);
+                    @Override
+                    public void onVisibilityChanged(int displayId, boolean hasFreeformTasks) {
+                        if (displayId == Display.DEFAULT_DISPLAY) {
+                            mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE,
+                                            hasFreeformTasks)
+                                    .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+                        }
+                        // TODO(b/278084491): update sysui state for changes on other displays
+                    }
+                }, mSysUiMainExecutor);
     }
 
     @Override
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index e2b568c..080be6d 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -196,6 +196,17 @@
             android:exported="false"
             android:permission="com.android.systemui.permission.SELF"
             android:excludeFromRecents="true" />
+
+        <activity
+            android:name="com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity"
+            android:exported="false"
+            android:permission="com.android.systemui.permission.SELF"
+            android:excludeFromRecents="true" >
+            <intent-filter>
+                <action android:name="com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation android:name="android.testing.TestableInstrumentation"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index f4df26d..8a05a37 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -682,8 +682,7 @@
     }
 
     @Test
-    public void testReinflateViewFlipper_asyncBouncerFlagOn() {
-        when(mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)).thenReturn(true);
+    public void testReinflateViewFlipper() {
         KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback =
                 controller -> {
                 };
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 2962c14..ddd9a08 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -151,7 +151,9 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 import org.mockito.internal.util.reflection.FieldSetter;
@@ -2737,6 +2739,36 @@
         verifyFingerprintAuthenticateCall();
     }
 
+    @Test
+    public void onTrustChangedCallbacksCalledBeforeOnTrustGrantedForCurrentUserCallback() {
+        // GIVEN device is interactive
+        deviceIsInteractive();
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged enabled=true
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                true /* newlyUnlocked */,
+                getCurrentUser() /* userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustChanged is called FIRST
+        final InOrder inOrder = Mockito.inOrder(callback);
+        inOrder.verify(callback).onTrustChanged(eq(getCurrentUser()));
+
+        // AND THEN onTrustGrantedForCurrentUser callback called
+        inOrder.verify(callback).onTrustGrantedForCurrentUser(
+                eq(true) /* dismissKeyguard */,
+                eq(true) /* newlyUnlocked */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+                eq(null) /* message */
+        );
+    }
+
     private void verifyFingerprintAuthenticateNeverCalled() {
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index a20875b..6e37ee7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -40,14 +40,19 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
 import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
 import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
 import org.junit.After
 import org.junit.Ignore
 import org.junit.Rule
@@ -87,13 +92,26 @@
     @Mock
     lateinit var interactionJankMonitor: InteractionJankMonitor
 
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val biometricPromptRepository = FakePromptRepository()
+    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
     private val credentialInteractor = FakeCredentialInteractor()
     private val bpCredentialInteractor = BiometricPromptCredentialInteractor(
         Dispatchers.Main.immediate,
         biometricPromptRepository,
         credentialInteractor
     )
+    private val displayStateInteractor = DisplayStateInteractorImpl(
+        testScope.backgroundScope,
+        mContext,
+        fakeExecutor,
+        rearDisplayStateRepository
+    )
+
+    private val authBiometricFingerprintViewModel = AuthBiometricFingerprintViewModel(
+        displayStateInteractor
+    )
     private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
 
     private var authContainer: TestAuthContainerView? = null
@@ -469,9 +487,10 @@
         lockPatternUtils,
         interactionJankMonitor,
         { bpCredentialInteractor },
+        { authBiometricFingerprintViewModel },
         { credentialViewModel },
         Handler(TestableLooper.get(this).looper),
-        FakeExecutor(FakeSystemClock())
+        fakeExecutor
     ) {
         override fun postOnAnimation(runnable: Runnable) {
             runnable.run()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 4f24b3a..a326cc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -93,6 +93,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
@@ -172,6 +173,8 @@
     @Mock
     private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor;
     @Mock
+    private AuthBiometricFingerprintViewModel mAuthBiometricFingerprintViewModel;
+    @Mock
     private CredentialViewModel mCredentialViewModel;
     @Mock
     private UdfpsUtils mUdfpsUtils;
@@ -995,8 +998,9 @@
                     () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
                     mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger,
                     mLogContextInteractor, () -> mBiometricPromptCredentialInteractor,
-                    () -> mCredentialViewModel, mInteractionJankMonitor, mHandler,
-                    mBackgroundExecutor, mVibratorHelper, mUdfpsUtils);
+                    () -> mAuthBiometricFingerprintViewModel, () -> mCredentialViewModel,
+                    mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper,
+                    mUdfpsUtils);
         }
 
         @Override
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 e6334cf..40d9009 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -55,6 +55,9 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.SysuiTestableContext
+import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
@@ -66,7 +69,9 @@
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.TestScope
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -90,6 +95,8 @@
 private const val DISPLAY_ID = 2
 private const val SENSOR_ID = 1
 
+private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
+
 @SmallTest
 @RoboPilotTest
 @RunWith(AndroidJUnit4::class)
@@ -112,7 +119,12 @@
 
     private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+    private lateinit var displayStateInteractor: DisplayStateInteractor
+
     private val executor = FakeExecutor(FakeSystemClock())
+    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+    private val testScope = TestScope(StandardTestDispatcher())
+
     private lateinit var overlayController: ISidefpsController
     private lateinit var sideFpsController: SideFpsController
 
@@ -142,6 +154,13 @@
                 FakeDeviceEntryFingerprintAuthRepository(),
                 FakeSystemClock(),
             )
+        displayStateInteractor =
+            DisplayStateInteractorImpl(
+                testScope.backgroundScope,
+                context,
+                executor,
+                rearDisplayStateRepository
+            )
 
         context.addMockSystemService(DisplayManager::class.java, displayManager)
         context.addMockSystemService(WindowManager::class.java, windowManager)
@@ -168,6 +187,7 @@
         isReverseDefaultRotation: Boolean = false,
         initInfo: DisplayInfo.() -> Unit = {},
         windowInsets: WindowInsets = insetsForSmallNavbar(),
+        inRearDisplayMode: Boolean = false,
         block: () -> Unit
     ) {
         this.deviceConfig = deviceConfig
@@ -228,6 +248,13 @@
             isReverseDefaultRotation
         )
 
+        val rearDisplayDeviceStates =
+            if (inRearDisplayMode) intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE) else intArrayOf()
+        sideFpsControllerContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.array.config_rearDisplayDeviceStates,
+            rearDisplayDeviceStates
+        )
+
         sideFpsController =
             SideFpsController(
                 sideFpsControllerContext,
@@ -237,12 +264,14 @@
                 activityTaskManager,
                 overviewProxyService,
                 displayManager,
+                displayStateInteractor,
                 executor,
                 handler,
                 alternateBouncerInteractor,
                 TestCoroutineScope(),
-                dumpManager,
+                dumpManager
             )
+        rearDisplayStateRepository.setIsInRearDisplayMode(inRearDisplayMode)
 
         overlayController =
             ArgumentCaptor.forClass(ISidefpsController::class.java)
@@ -584,10 +613,62 @@
             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)
@@ -624,7 +705,7 @@
      * in other rotations have been omitted.
      */
     @Test
-    fun verifiesIndicatorPlacementForXAlignedSensor_0() {
+    fun verifiesIndicatorPlacementForXAlignedSensor_0() =
         testWithDisplay(
             deviceConfig = DeviceConfig.X_ALIGNED,
             isReverseDefaultRotation = false,
@@ -641,7 +722,6 @@
             assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
             assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
         }
-    }
 
     /**
      * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
@@ -650,7 +730,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,
@@ -667,7 +747,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/data/repository/RearDisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt
new file mode 100644
index 0000000..dfe8d36
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.hardware.devicestate.DeviceStateManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+private const val NORMAL_DISPLAY_MODE_DEVICE_STATE = 2
+private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class RearDisplayStateRepositoryTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
+    @Mock private lateinit var deviceStateManager: DeviceStateManager
+    private lateinit var underTest: RearDisplayStateRepository
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+
+    @Captor
+    private lateinit var callbackCaptor: ArgumentCaptor<DeviceStateManager.DeviceStateCallback>
+
+    @Before
+    fun setUp() {
+        val rearDisplayDeviceStates = intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE)
+        mContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.array.config_rearDisplayDeviceStates,
+            rearDisplayDeviceStates
+        )
+
+        underTest =
+            RearDisplayStateRepositoryImpl(
+                testScope.backgroundScope,
+                mContext,
+                deviceStateManager,
+                fakeExecutor
+            )
+    }
+
+    @Test
+    fun updatesIsInRearDisplayMode_whenRearDisplayStateChanges() =
+        testScope.runTest {
+            val isInRearDisplayMode = collectLastValue(underTest.isInRearDisplayMode)
+            runCurrent()
+
+            val callback = deviceStateManager.captureCallback()
+
+            callback.onStateChanged(NORMAL_DISPLAY_MODE_DEVICE_STATE)
+            assertThat(isInRearDisplayMode()).isFalse()
+
+            callback.onStateChanged(REAR_DISPLAY_MODE_DEVICE_STATE)
+            assertThat(isInRearDisplayMode()).isTrue()
+        }
+}
+
+private fun DeviceStateManager.captureCallback() =
+    withArgCaptor<DeviceStateManager.DeviceStateCallback> {
+        verify(this@captureCallback).registerCallback(any(), capture())
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
new file mode 100644
index 0000000..2217c5c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
@@ -0,0 +1,84 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DisplayStateInteractorImplTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+
+    @Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider
+    private lateinit var interactor: DisplayStateInteractorImpl
+
+    @Before
+    fun setup() {
+        interactor =
+            DisplayStateInteractorImpl(
+                testScope.backgroundScope,
+                mContext,
+                fakeExecutor,
+                rearDisplayStateRepository
+            )
+        interactor.setScreenSizeFoldProvider(screenSizeFoldProvider)
+    }
+
+    @Test
+    fun isInRearDisplayModeChanges() =
+        testScope.runTest {
+            val isInRearDisplayMode = collectLastValue(interactor.isInRearDisplayMode)
+
+            rearDisplayStateRepository.setIsInRearDisplayMode(false)
+            assertThat(isInRearDisplayMode()).isFalse()
+
+            rearDisplayStateRepository.setIsInRearDisplayMode(true)
+            assertThat(isInRearDisplayMode()).isTrue()
+        }
+
+    @Test
+    fun isFoldedChanges() =
+        testScope.runTest {
+            val isFolded = collectLastValue(interactor.isFolded)
+            runCurrent()
+            val callback = screenSizeFoldProvider.captureCallback()
+
+            callback.onFoldUpdated(isFolded = true)
+            assertThat(isFolded()).isTrue()
+
+            callback.onFoldUpdated(isFolded = false)
+            assertThat(isFolded()).isFalse()
+        }
+}
+
+private fun FoldProvider.captureCallback() =
+    withArgCaptor<FoldProvider.FoldCallback> {
+        verify(this@captureCallback).registerCallback(capture(), any())
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt
new file mode 100644
index 0000000..0c210e5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt
@@ -0,0 +1,69 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.content.res.Configuration
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class AuthBiometricFingerprintViewModelTest : SysuiTestCase() {
+
+    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var interactor: DisplayStateInteractor
+    private lateinit var viewModel: AuthBiometricFingerprintViewModel
+
+    @Before
+    fun setup() {
+        interactor =
+            DisplayStateInteractorImpl(
+                testScope.backgroundScope,
+                mContext,
+                fakeExecutor,
+                rearDisplayStateRepository
+            )
+        viewModel = AuthBiometricFingerprintViewModel(interactor)
+    }
+
+    @Test
+    fun iconUpdates_onConfigurationChanged() {
+        testScope.runTest {
+            runCurrent()
+            val testConfig = Configuration()
+            val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
+            val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
+            val currentIcon = collectLastValue(viewModel.iconAsset)
+
+            testConfig.smallestScreenWidthDp = folded
+            viewModel.onConfigurationChanged(testConfig)
+            val foldedIcon = currentIcon()
+
+            testConfig.smallestScreenWidthDp = unfolded
+            viewModel.onConfigurationChanged(testConfig)
+            val unfoldedIcon = currentIcon()
+
+            assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+        }
+    }
+}
+
+internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
new file mode 100644
index 0000000..21516d49
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.clipboardoverlay
+
+import android.content.ContentResolver
+import android.content.Context
+import android.net.Uri
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.whenever
+import java.io.IOException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class ClipboardImageLoaderTest : SysuiTestCase() {
+    @Mock private lateinit var mockContext: Context
+
+    @Mock private lateinit var mockContentResolver: ContentResolver
+
+    private lateinit var clipboardImageLoader: ClipboardImageLoader
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun test_imageLoadSuccess() = runTest {
+        val testDispatcher = StandardTestDispatcher(this.testScheduler)
+        clipboardImageLoader =
+            ClipboardImageLoader(mockContext, testDispatcher, CoroutineScope(testDispatcher))
+        val testUri = Uri.parse("testUri")
+        whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+        whenever(mockContext.resources).thenReturn(context.resources)
+
+        clipboardImageLoader.load(testUri)
+
+        verify(mockContentResolver).loadThumbnail(eq(testUri), any(), any())
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    @Throws(IOException::class)
+    fun test_imageLoadFailure() = runTest {
+        val testDispatcher = StandardTestDispatcher(this.testScheduler)
+        clipboardImageLoader =
+            ClipboardImageLoader(mockContext, testDispatcher, CoroutineScope(testDispatcher))
+        val testUri = Uri.parse("testUri")
+        whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+        whenever(mockContext.resources).thenReturn(context.resources)
+
+        val res = clipboardImageLoader.load(testUri)
+
+        verify(mockContentResolver).loadThumbnail(eq(testUri), any(), any())
+        assertNull(res)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index fe5fa1f..39fb7b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -25,6 +25,7 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_EXPANDED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -90,6 +91,8 @@
     @Mock
     private ClipboardOverlayUtils mClipboardUtils;
     @Mock
+    private ClipboardImageLoader mClipboardImageLoader;
+    @Mock
     private UiEventLogger mUiEventLogger;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
     private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@@ -120,6 +123,7 @@
         mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
                 new ClipData.Item("Test Item"));
 
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests
 
         mOverlayController = new ClipboardOverlayController(
                 mContext,
@@ -131,6 +135,7 @@
                 mFeatureFlags,
                 mClipboardUtils,
                 mExecutor,
+                mClipboardImageLoader,
                 mUiEventLogger);
         verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
         mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -142,6 +147,69 @@
     }
 
     @Test
+    public void test_setClipData_invalidImageData_legacy() {
+        ClipData clipData = new ClipData("", new String[]{"image/png"},
+                new ClipData.Item(Uri.parse("")));
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+
+        mOverlayController.setClipData(clipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_nonImageUri_legacy() {
+        ClipData clipData = new ClipData("", new String[]{"resource/png"},
+                new ClipData.Item(Uri.parse("")));
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+
+        mOverlayController.setClipData(clipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_textData_legacy() {
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+        mOverlayController.setClipData(mSampleClipData, "abc");
+
+        verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "abc");
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_sensitiveTextData_legacy() {
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+        ClipDescription description = mSampleClipData.getDescription();
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
+        description.setExtras(b);
+        ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
+        mOverlayController.setClipData(data, "");
+
+        verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_repeatedCalls_legacy() {
+        when(mAnimator.isRunning()).thenReturn(true);
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+
+        mOverlayController.setClipData(mSampleClipData, "");
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
     public void test_setClipData_invalidImageData() {
         ClipData clipData = new ClipData("", new String[]{"image/png"},
                 new ClipData.Item(Uri.parse("")));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index b9cfc65..e981d62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -25,13 +25,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.IActivityTaskManager;
 import android.app.IApplicationThread;
 import android.app.ProfilerInfo;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -59,13 +57,14 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class WorkLockActivityControllerTest extends SysuiTestCase {
-    private static final int USER_ID = 333;
+    private static final int TASK_USER_ID = 333;
+    private static final int PROFILE_USER_ID = 555;
     private static final int TASK_ID = 444;
     private static final ActivityManager.RunningTaskInfo TASK_INFO =
             new ActivityManager.RunningTaskInfo();
 
     static {
-        TASK_INFO.userId = USER_ID;
+        TASK_INFO.userId = TASK_USER_ID;
         TASK_INFO.taskId = TASK_ID;
     }
 
@@ -101,10 +100,10 @@
         setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_SUCCESS);
 
         // And the controller receives a message saying the profile is locked,
-        mTaskStackListener.onTaskProfileLocked(TASK_INFO);
+        mTaskStackListener.onTaskProfileLocked(TASK_INFO, PROFILE_USER_ID);
 
         // The overlay should start and the task the activity started in should not be removed.
-        verifyStartActivity(TASK_ID, true /*taskOverlay*/);
+        verifyStartActivity(TASK_ID, true /*taskOverlay*/, PROFILE_USER_ID);
         verify(mIActivityTaskManager, never()).removeTask(anyInt() /*taskId*/);
     }
 
@@ -114,11 +113,11 @@
         setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_CLASS_NOT_FOUND);
 
         // And the controller receives a message saying the profile is locked,
-        mTaskStackListener.onTaskProfileLocked(TASK_INFO);
+        mTaskStackListener.onTaskProfileLocked(TASK_INFO, PROFILE_USER_ID);
 
         // The task the activity started in should be removed to prevent the locked task from
         // being shown.
-        verifyStartActivity(TASK_ID, true /*taskOverlay*/);
+        verifyStartActivity(TASK_ID, true /*taskOverlay*/, PROFILE_USER_ID);
         verify(mIActivityTaskManager).removeTask(TASK_ID);
     }
 
@@ -141,12 +140,13 @@
                 eq(ActivityManager.getCurrentUser()));
     }
 
-    private void verifyStartActivity(int taskId, boolean taskOverlay) throws Exception {
+    private void verifyStartActivity(int taskId, boolean taskOverlay, int profileUserId)
+            throws Exception {
         verify(mIActivityTaskManager).startActivityAsUser(
                 eq((IApplicationThread) null),
                 eq((String) null),
                 eq((String) null),
-                any(Intent.class),
+                argThat(hasUserId(profileUserId)),
                 eq((String) null),
                 eq((IBinder) null),
                 eq((String) null),
@@ -157,24 +157,15 @@
                 eq(ActivityManager.getCurrentUser()));
     }
 
-    private static ArgumentMatcher<Intent> hasComponent(final Context context,
-            final Class<? extends Activity> activityClass) {
-        return new ArgumentMatcher<Intent>() {
-            @Override
-            public boolean matches(Intent intent) {
-                return new ComponentName(context, activityClass).equals(intent.getComponent());
-            }
-        };
+    private static ArgumentMatcher<Intent> hasUserId(int userId) {
+        return intent -> intent.getIntExtra(Intent.EXTRA_USER_ID, -1) == userId;
     }
 
     private static ArgumentMatcher<Bundle> hasOptions(final int taskId, final boolean overlay) {
-        return new ArgumentMatcher<Bundle>() {
-            @Override
-            public boolean matches(Bundle item) {
-                final ActivityOptions options = ActivityOptions.fromBundle(item);
-                return (options.getLaunchTaskId() == taskId)
-                        && (options.getTaskOverlay() == overlay);
-            }
+        return item -> {
+            final ActivityOptions options = ActivityOptions.fromBundle(item);
+            return (options.getLaunchTaskId() == taskId)
+                    && (options.getTaskOverlay() == overlay);
         };
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
index a003e1d..43e430f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
@@ -19,7 +19,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
+import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertTrue
 import org.junit.Test
 
 @SmallTest
@@ -326,4 +328,152 @@
 
         assertThat(underTest.getVal()).doesNotContain(IS_INITIAL_PREFIX)
     }
+
+    @Test
+    fun constructor_columnAndValueTooLong_truncated() {
+        val underTest =
+            TableChange(
+                columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+                columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+                type = TableChange.DataType.STRING,
+                str = "V".repeat(MAX_STRING_LENGTH + 10),
+            )
+
+        assertThat(underTest.getName()).contains("P".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+        assertThat(underTest.getName()).contains("N".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+        assertThat(underTest.getVal()).isEqualTo("V".repeat(MAX_STRING_LENGTH))
+    }
+
+    @Test
+    fun constructor_columnNameNotTooLong_noReallocation() {
+        val inputColumnName = "fakeName"
+        val inputValue = "fakeValue"
+        val underTest =
+            TableChange(
+                columnPrefix = "",
+                columnName = inputColumnName,
+                type = TableChange.DataType.STRING,
+                str = inputValue,
+            )
+
+        // Use referential equality to verify we didn't reallocate a new string when the string is
+        // *not* too long.
+        assertTrue(underTest.getColumnName() === inputColumnName)
+    }
+
+    @Test
+    fun reset_columnPrefixTooLong_truncated() {
+        val underTest = TableChange()
+
+        underTest.reset(
+            timestamp = 1L,
+            columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+            columnName = "name",
+            isInitial = false,
+        )
+
+        assertThat(underTest.getName()).contains("P".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun reset_columnNameTooLong_truncated() {
+        val underTest = TableChange()
+
+        underTest.reset(
+            timestamp = 1L,
+            columnPrefix = "prefix",
+            columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+            isInitial = false,
+        )
+
+        assertThat(underTest.getName()).contains("N".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun reset_columnNameNotTooLong_noReallocation() {
+        val underTest = TableChange()
+        val shortColumnName = "shortColumnName"
+
+        underTest.reset(
+            timestamp = 1L,
+            columnPrefix = "prefix",
+            columnName = shortColumnName,
+            isInitial = false,
+        )
+
+        // Use referential equality to verify we didn't reallocate a new string when the string is
+        // *not* too long.
+        assertTrue(underTest.getColumnName() === shortColumnName)
+    }
+
+    @Test
+    fun setString_valueTooLong_truncated() {
+        val underTest = TableChange()
+
+        underTest.set("V".repeat(MAX_STRING_LENGTH + 1))
+
+        assertThat(underTest.getVal()).isEqualTo("V".repeat(MAX_STRING_LENGTH))
+    }
+
+    @Test
+    fun updateTo_newColumnPrefixTooLong_truncated() {
+        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+        underTest.set(42)
+
+        val new =
+            TableChange(
+                columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+                columnName = "name",
+            )
+        underTest.updateTo(new)
+
+        assertThat(underTest.getName()).contains("P".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun updateTo_newColumnNameTooLong_truncated() {
+        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+        underTest.set(42)
+
+        val new =
+            TableChange(
+                columnPrefix = "prefix",
+                columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+            )
+        underTest.updateTo(new)
+
+        assertThat(underTest.getName()).contains("N".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun updateTo_columnNameNotTooLong_noReallocation() {
+        val underTest = TableChange()
+        val shortColumnName = "shortColumnName"
+        val new = TableChange(columnPrefix = "prefix", columnName = shortColumnName)
+
+        underTest.updateTo(new)
+
+        // Use referential equality to verify we didn't reallocate a new string when the string is
+        // *not* too long.
+        assertTrue(underTest.getColumnName() === shortColumnName)
+    }
+
+    @Test
+    fun updateTo_newValTooLong_truncated() {
+        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+        underTest.set("value")
+
+        val new = TableChange()
+        new.set("V".repeat(MAX_STRING_LENGTH + 10))
+
+        underTest.updateTo(new)
+
+        assertThat(underTest.getVal()).isEqualTo("V".repeat(MAX_STRING_LENGTH))
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
index a2b2322..f867fc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
+import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH
 import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.plugins.log.LogcatEchoTracker
 import com.android.systemui.util.mockito.any
@@ -576,6 +577,112 @@
     }
 
     @Test
+    fun dumpChanges_tooLongColumnPrefix_viaLogChange_truncated() {
+        underTest.logChange(
+            prefix = "P".repeat(MAX_STRING_LENGTH + 10),
+            columnName = "name",
+            value = true,
+        )
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).contains("P".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun dumpChanges_tooLongColumnPrefix_viaLogDiffs_truncated() {
+        val prevDiffable = object : TestDiffable() {}
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("status", "value")
+                }
+            }
+
+        // WHEN the column prefix is too large
+        underTest.logDiffs(
+            columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+            prevDiffable,
+            nextDiffable,
+        )
+
+        val dumpedString = dumpChanges()
+
+        // THEN it's truncated to the max length
+        assertThat(dumpedString).contains("P".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun dumpChanges_tooLongColumnName_viaLogChange_truncated() {
+        underTest.logChange(
+            prefix = "prefix",
+            columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+            value = 10,
+        )
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).contains("N".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun dumpChanges_tooLongColumnName_viaLogDiffs_truncated() {
+        val prevDiffable = object : TestDiffable() {}
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    // WHEN the column name is too large
+                    row.logChange(columnName = "N".repeat(MAX_STRING_LENGTH + 10), "value")
+                }
+            }
+
+        underTest.logDiffs(columnPrefix = "prefix", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        // THEN it's truncated to the max length
+        assertThat(dumpedString).contains("N".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun dumpChanges_tooLongValue_viaLogChange_truncated() {
+        underTest.logChange(
+            prefix = "prefix",
+            columnName = "name",
+            value = "V".repeat(MAX_STRING_LENGTH + 10),
+        )
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).contains("V".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("V".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun dumpChanges_tooLongValue_viaLogDiffs_truncated() {
+        val prevDiffable = object : TestDiffable() {}
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    // WHEN the value is too large
+                    row.logChange("columnName", value = "V".repeat(MAX_STRING_LENGTH + 10))
+                }
+            }
+
+        underTest.logDiffs(columnPrefix = "prefix", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        // THEN it's truncated to the max length
+        assertThat(dumpedString).contains("V".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("V".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
     fun dumpChanges_rotatesIfBufferIsFull() {
         lateinit var valToDump: String
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 0a1db60..d428db7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -40,7 +40,6 @@
 import androidx.media.utils.MediaConstants
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId
-import com.android.internal.statusbar.IStatusBarService
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.InstanceIdSequenceFake
 import com.android.systemui.R
@@ -131,7 +130,6 @@
     @Mock lateinit var activityStarter: ActivityStarter
     @Mock lateinit var smartspaceManager: SmartspaceManager
     @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock lateinit var statusBarService: IStatusBarService
     lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
     @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -194,8 +192,7 @@
                 mediaFlags = mediaFlags,
                 logger = logger,
                 smartspaceManager = smartspaceManager,
-                keyguardUpdateMonitor = keyguardUpdateMonitor,
-                statusBarService = statusBarService,
+                keyguardUpdateMonitor = keyguardUpdateMonitor
             )
         verify(tunerService)
             .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -520,136 +517,19 @@
     }
 
     @Test
-    fun testOnNotificationAdded_emptyTitle_notLoaded() {
-        // GIVEN that the manager has a notification with an empty title.
+    fun testOnNotificationRemoved_emptyTitle_notConverted() {
+        // GIVEN that the manager has a notification with a resume action and empty title.
         whenever(controller.metadata)
             .thenReturn(
                 metadataBuilder
                     .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
                     .build()
             )
-        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(statusBarService)
-            .onNotificationError(
-                eq(PACKAGE_NAME),
-                eq(mediaNotification.tag),
-                eq(mediaNotification.id),
-                eq(mediaNotification.uid),
-                eq(mediaNotification.initialPid),
-                eq(MEDIA_TITLE_ERROR_MESSAGE),
-                eq(mediaNotification.user.identifier)
-            )
-        verify(listener, never())
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-        verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
-        verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
-    }
-
-    @Test
-    fun testOnNotificationAdded_blankTitle_notLoaded() {
-        // GIVEN that the manager has a notification with a blank title.
-        whenever(controller.metadata)
-            .thenReturn(
-                metadataBuilder
-                    .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
-                    .build()
-            )
-        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(statusBarService)
-            .onNotificationError(
-                eq(PACKAGE_NAME),
-                eq(mediaNotification.tag),
-                eq(mediaNotification.id),
-                eq(mediaNotification.uid),
-                eq(mediaNotification.initialPid),
-                eq(MEDIA_TITLE_ERROR_MESSAGE),
-                eq(mediaNotification.user.identifier)
-            )
-        verify(listener, never())
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-        verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
-        verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
-    }
-
-    @Test
-    fun testOnNotificationUpdated_invalidTitle_logMediaRemoved() {
-        addNotificationAndLoad()
-        val data = mediaDataCaptor.value
-
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-
-        reset(listener)
-        whenever(controller.metadata)
-            .thenReturn(
-                metadataBuilder
-                    .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
-                    .build()
-            )
-        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(statusBarService)
-            .onNotificationError(
-                eq(PACKAGE_NAME),
-                eq(mediaNotification.tag),
-                eq(mediaNotification.id),
-                eq(mediaNotification.uid),
-                eq(mediaNotification.initialPid),
-                eq(MEDIA_TITLE_ERROR_MESSAGE),
-                eq(mediaNotification.user.identifier)
-            )
-        verify(listener, never())
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-        verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
-    }
-
-    @Test
-    fun testOnNotificationRemoved_emptyTitle_notConverted() {
-        // GIVEN that the manager has a notification with a resume action and empty title.
         addNotificationAndLoad()
         val data = mediaDataCaptor.value
         val instanceId = data.instanceId
         assertThat(data.resumption).isFalse()
-        mediaDataManager.onMediaDataLoaded(
-            KEY,
-            null,
-            data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {})
-        )
+        mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
 
         // WHEN the notification is removed
         reset(listener)
@@ -674,15 +554,17 @@
     @Test
     fun testOnNotificationRemoved_blankTitle_notConverted() {
         // GIVEN that the manager has a notification with a resume action and blank title.
+        whenever(controller.metadata)
+            .thenReturn(
+                metadataBuilder
+                    .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
+                    .build()
+            )
         addNotificationAndLoad()
         val data = mediaDataCaptor.value
         val instanceId = data.instanceId
         assertThat(data.resumption).isFalse()
-        mediaDataManager.onMediaDataLoaded(
-            KEY,
-            null,
-            data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {})
-        )
+        mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
 
         // WHEN the notification is removed
         reset(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
new file mode 100644
index 0000000..36b913f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.notetask
+
+import android.content.Context
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+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
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class LaunchNotesRoleSettingsTrampolineActivityTest : SysuiTestCase() {
+
+    @Mock lateinit var noteTaskController: NoteTaskController
+
+    @Rule
+    @JvmField
+    val activityRule =
+        ActivityTestRule<LaunchNotesRoleSettingsTrampolineActivity>(
+            /* activityFactory= */ object :
+                SingleActivityFactory<LaunchNotesRoleSettingsTrampolineActivity>(
+                    LaunchNotesRoleSettingsTrampolineActivity::class.java
+                ) {
+                override fun create(intent: Intent?) =
+                    LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
+            },
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
+        )
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @After
+    fun tearDown() {
+        activityRule.finishActivity()
+    }
+
+    @Test
+    fun startActivity_noAction_shouldLaunchNotesRoleSettingTaskWithNullEntryPoint() {
+        activityRule.launchActivity(/* startIntent= */ null)
+
+        verify(noteTaskController).startNotesRoleSetting(any(Context::class.java), eq(null))
+    }
+
+    @Test
+    fun startActivity_quickAffordanceAction_shouldLaunchNotesRoleSettingTaskWithQuickAffordanceEntryPoint() { // ktlint-disable max-line-length
+        activityRule.launchActivity(Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE))
+
+        verify(noteTaskController)
+            .startNotesRoleSetting(any(Context::class.java), eq(QUICK_AFFORDANCE))
+    }
+}
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 5dbcd33..5f89705 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -47,6 +47,9 @@
 import com.android.systemui.notetask.NoteTaskController.Companion.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
 import com.android.systemui.notetask.NoteTaskController.Companion.SETTINGS_CREATE_NOTE_TASK_SHORTCUT_COMPONENT
 import com.android.systemui.notetask.NoteTaskController.Companion.SHORTCUT_ID
+import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS
+import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
 import com.android.systemui.settings.FakeUserTracker
@@ -493,7 +496,7 @@
             )
             .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyZeroInteractions(context, bubbles, eventLogger)
     }
@@ -509,7 +512,7 @@
             )
             .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyZeroInteractions(context, bubbles, eventLogger)
     }
@@ -525,7 +528,7 @@
             )
             .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
     }
@@ -541,7 +544,7 @@
             )
             .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
     }
@@ -553,7 +556,7 @@
         whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
         userTracker.set(listOf(mainUserInfo), mainAndWorkProfileUsers.indexOf(mainUserInfo))
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle)
     }
@@ -563,7 +566,7 @@
         whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
         userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyNoteTaskOpenInBubbleInUser(workUserInfo.userHandle)
     }
@@ -734,6 +737,129 @@
     }
     // endregion
 
+    // region getUserForHandlingNotesTaking
+    @Test
+    fun getUserForHandlingNotesTaking_cope_quickAffordance_shouldReturnWorkProfileUser() {
+        whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
+
+        assertThat(user).isEqualTo(UserHandle.of(workUserInfo.id))
+    }
+
+    @Test
+    fun getUserForHandlingNotesTaking_cope_tailButton_shouldReturnWorkProfileUser() {
+        whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(TAIL_BUTTON)
+
+        assertThat(user).isEqualTo(UserHandle.of(workUserInfo.id))
+    }
+
+    @Test
+    fun getUserForHandlingNotesTaking_cope_appClip_shouldReturnCurrentUser() {
+        whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(APP_CLIPS)
+
+        assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+
+    @Test
+    fun getUserForHandlingNotesTaking_noManagement_quickAffordance_shouldReturnCurrentUser() {
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
+
+        assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+
+    @Test
+    fun getUserForHandlingNotesTaking_noManagement_tailButton_shouldReturnCurrentUser() {
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(TAIL_BUTTON)
+
+        assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+
+    @Test
+    fun getUserForHandlingNotesTaking_noManagement_appClip_shouldReturnCurrentUser() {
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(APP_CLIPS)
+
+        assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+    // endregion
+
+    // startregion startNotesRoleSetting
+    @Test
+    fun startNotesRoleSetting_cope_quickAffordance_shouldStartNoteRoleIntentWithWorkProfileUser() {
+        whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        createNoteTaskController().startNotesRoleSetting(context, QUICK_AFFORDANCE)
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
+        }
+        assertThat(userCaptor.value).isEqualTo(UserHandle.of(workUserInfo.id))
+    }
+
+    @Test
+    fun startNotesRoleSetting_cope_nullEntryPoint_shouldStartNoteRoleIntentWithCurrentUser() {
+        whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        createNoteTaskController().startNotesRoleSetting(context, entryPoint = null)
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
+        }
+        assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+
+    @Test
+    fun startNotesRoleSetting_noManagement_quickAffordance_shouldStartNoteRoleIntentWithCurrentUser() { // ktlint-disable max-line-length
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        createNoteTaskController().startNotesRoleSetting(context, QUICK_AFFORDANCE)
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
+        }
+        assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+
+    @Test
+    fun startNotesRoleSetting_noManagement_nullEntryPoint_shouldStartNoteRoleIntentWithCurrentUser() { // ktlint-disable max-line-length
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        createNoteTaskController().startNotesRoleSetting(context, entryPoint = null)
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
+        }
+        assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+    // endregion
+
     private companion object {
         const val NOTE_TASK_SHORT_LABEL = "Notetaking"
         const val NOTE_TASK_ACTIVITY_NAME = "NoteTaskActivity"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index 42ef2b5..4526580 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -18,7 +18,12 @@
 
 package com.android.systemui.notetask.quickaffordance
 
+import android.app.role.RoleManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.hardware.input.InputSettings
+import android.os.UserHandle
 import android.os.UserManager
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
@@ -31,11 +36,18 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
+import com.android.systemui.notetask.NoteTaskInfoResolver
+import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR
 import com.android.systemui.stylus.StylusManager
+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.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -45,6 +57,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.anyString
 import org.mockito.Mockito.verify
 import org.mockito.MockitoSession
 import org.mockito.quality.Strictness
@@ -58,6 +71,8 @@
     @Mock lateinit var stylusManager: StylusManager
     @Mock lateinit var repository: KeyguardQuickAffordanceRepository
     @Mock lateinit var userManager: UserManager
+    @Mock lateinit var roleManager: RoleManager
+    @Mock lateinit var packageManager: PackageManager
 
     private lateinit var mockitoSession: MockitoSession
 
@@ -69,6 +84,23 @@
                 .mockStatic(InputSettings::class.java)
                 .strictness(Strictness.LENIENT)
                 .startMocking()
+
+        whenever(
+                packageManager.getApplicationInfoAsUser(
+                    anyString(),
+                    any(ApplicationInfoFlags::class.java),
+                    any(UserHandle::class.java)
+                )
+            )
+            .thenReturn(ApplicationInfo())
+        whenever(controller.getUserForHandlingNotesTaking(any())).thenReturn(UserHandle.SYSTEM)
+        whenever(
+                roleManager.getRoleHoldersAsUser(
+                    eq(RoleManager.ROLE_NOTES),
+                    any(UserHandle::class.java)
+                )
+            )
+            .thenReturn(listOf("com.google.test.notes"))
     }
 
     @After
@@ -85,6 +117,9 @@
             keyguardMonitor = mock(),
             lazyRepository = { repository },
             isEnabled = isEnabled,
+            backgroundExecutor = FakeExecutor(FakeSystemClock()),
+            roleManager = roleManager,
+            noteTaskInfoResolver = NoteTaskInfoResolver(roleManager, packageManager)
         )
 
     private fun createLockScreenStateVisible(): LockScreenState =
@@ -112,6 +147,27 @@
     }
 
     @Test
+    fun lockScreenState_stylusUsed_userUnlocked_isSelected_noDefaultNotesAppSet_shouldEmitHidden() =
+        runTest {
+            TestConfig()
+                .setStylusEverUsed(true)
+                .setUserUnlocked(true)
+                .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>())
+            whenever(
+                    roleManager.getRoleHoldersAsUser(
+                        eq(RoleManager.ROLE_NOTES),
+                        any(UserHandle::class.java)
+                    )
+                )
+                .thenReturn(emptyList())
+
+            val underTest = createUnderTest()
+            val actual by collectLastValue(underTest.lockScreenState)
+
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
+
+    @Test
     fun lockScreenState_stylusUnused_userUnlocked_isSelected_shouldEmitHidden() = runTest {
         TestConfig()
             .setStylusEverUsed(false)
@@ -217,6 +273,39 @@
         verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
     }
 
+    // region getPickerScreenState
+    @Test
+    fun getPickerScreenState_defaultNoteAppSet_shouldReturnDefault() = runTest {
+        val underTest = createUnderTest(isEnabled = true)
+
+        assertThat(underTest.getPickerScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
+    }
+
+    @Test
+    fun getPickerScreenState_nodefaultNoteAppSet_shouldReturnDisable() = runTest {
+        val underTest = createUnderTest(isEnabled = true)
+        whenever(
+                roleManager.getRoleHoldersAsUser(
+                    eq(RoleManager.ROLE_NOTES),
+                    any(UserHandle::class.java)
+                )
+            )
+            .thenReturn(emptyList())
+
+        assertThat(underTest.getPickerScreenState())
+            .isEqualTo(
+                KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+                    listOf("Select a default notes app to use the notetaking shortcut"),
+                    actionText = "Select app",
+                    actionComponentName =
+                        "${context.packageName}$COMPONENT_NAME_SEPARATOR" +
+                            "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE"
+                )
+            )
+    }
+    // endregion
+
     private inner class TestConfig {
 
         fun setStylusEverUsed(value: Boolean) = also {
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 e106741..41545fc 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
@@ -30,6 +30,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.testing.TestableResources;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
@@ -94,14 +95,18 @@
     private RotationLockController mController;
     private TestableLooper mTestableLooper;
     private RotationLockTile mLockTile;
+    private TestableResources mTestableResources;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
+        mTestableResources = mContext.getOrCreateTestableResources();
 
         when(mHost.getContext()).thenReturn(mContext);
         when(mHost.getUserContext()).thenReturn(mContext);
+        mTestableResources.addOverride(com.android.internal.R.bool.config_allowRotationResolver,
+                true);
 
         mController = new RotationLockControllerImpl(mRotationPolicyWrapper,
                 mDeviceStateRotationLockSettingController, DEFAULT_SETTINGS);
@@ -208,6 +213,32 @@
     }
 
     @Test
+    public void testSecondaryString_rotationResolverDisabled_isEmpty() {
+        mTestableResources.addOverride(com.android.internal.R.bool.config_allowRotationResolver,
+                false);
+        mLockTile = new RotationLockTile(
+                mHost,
+                mUiEventLogger,
+                mTestableLooper.getLooper(),
+                new Handler(mTestableLooper.getLooper()),
+                new FalsingManagerFake(),
+                mMetricsLogger,
+                mStatusBarStateController,
+                mActivityStarter,
+                mQSLogger,
+                mController,
+                mPrivacyManager,
+                mBatteryController,
+                new FakeSettings()
+        );
+
+        mLockTile.refreshState();
+        mTestableLooper.processAllMessages();
+
+        assertEquals("", mLockTile.getState().secondaryLabel.toString());
+    }
+
+    @Test
     public void testIcon_whenDisabled_isOffState() {
         QSTile.BooleanState state = new QSTile.BooleanState();
         disableAutoRotation();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index a1d78cb..7c30843b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -94,6 +93,7 @@
         doReturn(mContext.getUserId()).when(mRecordingService).getUserId();
         doReturn(mContext.getPackageName()).when(mRecordingService).getPackageName();
         doReturn(mContext.getContentResolver()).when(mRecordingService).getContentResolver();
+        doReturn(mContext.getResources()).when(mRecordingService).getResources();
 
         // Mock notifications
         doNothing().when(mRecordingService).createRecordingNotification();
@@ -101,7 +101,7 @@
         doReturn(mNotification).when(mRecordingService).createSaveNotification(any());
         doNothing().when(mRecordingService).createErrorNotification();
         doNothing().when(mRecordingService).showErrorToast(anyInt());
-        doNothing().when(mRecordingService).stopForeground(anyBoolean());
+        doNothing().when(mRecordingService).stopForeground(anyInt());
 
         doNothing().when(mRecordingService).startForeground(anyInt(), any());
         doReturn(mScreenMediaRecorder).when(mRecordingService).getRecorder();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index beaf300..c49f179 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -356,6 +356,7 @@
     @Test
     fun ignoreShadeBlurUntilHidden_schedulesFrame() {
         notificationShadeDepthController.blursDisabledForAppLaunch = true
+        verify(blurUtils).prepareBlur(any(), anyInt())
         verify(choreographer)
             .postFrameCallback(eq(notificationShadeDepthController.updateBlurCallback))
     }
@@ -419,6 +420,7 @@
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(notificationShadeWindowController).setBackgroundBlurRadius(eq(0))
         verify(wallpaperController).setNotificationShadeZoom(eq(1f))
+        verify(blurUtils).prepareBlur(any(), eq(0))
         verify(blurUtils).applyBlur(eq(viewRootImpl), eq(0), eq(false))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index b80b825..c282c1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -21,6 +21,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -49,7 +51,7 @@
     fun calculateWidthFor_oneIcon_widthForOneIcon() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f),
                 /* actual= */ 30f)
@@ -59,7 +61,7 @@
     fun calculateWidthFor_fourIcons_widthForFourIcons() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f),
                 /* actual= */ 60f)
@@ -69,7 +71,7 @@
     fun calculateWidthFor_fiveIcons_widthForFourIcons() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f),
                 /* actual= */ 60f)
     }
@@ -78,7 +80,7 @@
     fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         val icon = mockStatusBarIcon()
         iconContainer.addView(icon)
@@ -99,7 +101,7 @@
     fun calculateIconXTranslations_shortShelfFourIcons_atCorrectXWithoutOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         val iconOne = mockStatusBarIcon()
         val iconTwo = mockStatusBarIcon()
@@ -128,7 +130,7 @@
     fun calculateIconXTranslations_shortShelfFiveIcons_atCorrectXWithOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         val iconOne = mockStatusBarIcon()
         val iconTwo = mockStatusBarIcon()
@@ -154,6 +156,55 @@
     }
 
     @Test
+    fun calculateIconXTranslations_givenWidthEnoughForThreeIcons_atCorrectXWithoutOverflowDot() {
+        iconContainer.setActualPaddingStart(0f)
+        iconContainer.setActualPaddingEnd(0f)
+        iconContainer.setActualLayoutWidth(30)
+        iconContainer.setIconSize(10)
+
+        val iconOne = mockStatusBarIcon()
+        val iconTwo = mockStatusBarIcon()
+        val iconThree = mockStatusBarIcon()
+
+        iconContainer.addView(iconOne)
+        iconContainer.addView(iconTwo)
+        iconContainer.addView(iconThree)
+        assertEquals(3, iconContainer.childCount)
+
+        iconContainer.calculateIconXTranslations()
+        assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
+        assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
+        assertEquals(20f, iconContainer.getIconState(iconThree).xTranslation)
+        assertFalse(iconContainer.areIconsOverflowing())
+    }
+
+    @Test
+    fun calculateIconXTranslations_givenWidthNotEnoughForFourIcons_atCorrectXWithOverflowDot() {
+        iconContainer.setActualPaddingStart(0f)
+        iconContainer.setActualPaddingEnd(0f)
+        iconContainer.setActualLayoutWidth(35)
+        iconContainer.setIconSize(10)
+
+        val iconOne = mockStatusBarIcon()
+        val iconTwo = mockStatusBarIcon()
+        val iconThree = mockStatusBarIcon()
+        val iconFour = mockStatusBarIcon()
+
+        iconContainer.addView(iconOne)
+        iconContainer.addView(iconTwo)
+        iconContainer.addView(iconThree)
+        iconContainer.addView(iconFour)
+        assertEquals(4, iconContainer.childCount)
+
+        iconContainer.calculateIconXTranslations()
+        assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
+        assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
+        assertEquals(STATE_DOT, iconContainer.getIconState(iconThree).visibleState)
+        assertEquals(STATE_HIDDEN, iconContainer.getIconState(iconFour).visibleState)
+        assertTrue(iconContainer.areIconsOverflowing())
+    }
+
+    @Test
     fun shouldForceOverflow_appearingAboveSpeedBump_true() {
         val forceOverflow = iconContainer.shouldForceOverflow(
                 /* i= */ 1,
@@ -161,7 +212,7 @@
                 /* iconAppearAmount= */ 1f,
                 /* maxVisibleIcons= */ 5
         )
-        assertTrue(forceOverflow);
+        assertTrue(forceOverflow)
     }
 
     @Test
@@ -172,7 +223,7 @@
                 /* iconAppearAmount= */ 0f,
                 /* maxVisibleIcons= */ 5
         )
-        assertTrue(forceOverflow);
+        assertTrue(forceOverflow)
     }
 
     @Test
@@ -183,7 +234,7 @@
                 /* iconAppearAmount= */ 0f,
                 /* maxVisibleIcons= */ 5
         )
-        assertFalse(forceOverflow);
+        assertFalse(forceOverflow)
     }
 
     @Test
@@ -210,6 +261,17 @@
     }
 
     @Test
+    fun isOverflowing_lastChildXGreaterThanDotX_true() {
+        val isOverflowing = iconContainer.isOverflowing(
+                /* isLastChild= */ true,
+                /* translationX= */ 9f,
+                /* layoutEnd= */ 10f,
+                /* iconSize= */ 2f,
+        )
+        assertTrue(isOverflowing)
+    }
+
+    @Test
     fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() {
         val isOverflowing = iconContainer.isOverflowing(
                 /* isLastChild= */ true,
@@ -253,7 +315,7 @@
         assertTrue(isOverflowing)
     }
 
-    private fun mockStatusBarIcon() : StatusBarIconView {
+    private fun mockStatusBarIcon(): StatusBarIconView {
         val iconView = mock(StatusBarIconView::class.java)
         whenever(iconView.width).thenReturn(10)
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt
new file mode 100644
index 0000000..fd91391
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.biometrics.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeRearDisplayStateRepository : RearDisplayStateRepository {
+    private val _isInRearDisplayMode = MutableStateFlow<Boolean>(false)
+    override val isInRearDisplayMode: StateFlow<Boolean> = _isInRearDisplayMode.asStateFlow()
+
+    fun setIsInRearDisplayMode(isInRearDisplayMode: Boolean) {
+        _isInRearDisplayMode.value = isInRearDisplayMode
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
deleted file mode 100644
index 715697d..0000000
--- a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * 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.server.autofill;
-
-import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
-
-import static com.android.server.autofill.Helper.sVerbose;
-
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.ICancellationSignal;
-import android.os.RemoteException;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
-import android.service.autofill.IFillCallback;
-import android.service.autofill.SaveInfo;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Slog;
-import android.view.autofill.AutofillId;
-import android.view.autofill.IAutoFillManagerClient;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AndroidFuture;
-
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Maintains a client suggestions session with the
- * {@link android.view.autofill.AutofillRequestCallback} through the {@link IAutoFillManagerClient}.
- *
- */
-final class ClientSuggestionsSession {
-
-    private static final String TAG = "ClientSuggestionsSession";
-    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 15 * DateUtils.SECOND_IN_MILLIS;
-
-    private final int mSessionId;
-    private final IAutoFillManagerClient mClient;
-    private final Handler mHandler;
-    private final ComponentName mComponentName;
-
-    private final RemoteFillService.FillServiceCallbacks mCallbacks;
-
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private AndroidFuture<FillResponse> mPendingFillRequest;
-    @GuardedBy("mLock")
-    private int mPendingFillRequestId = INVALID_REQUEST_ID;
-
-    ClientSuggestionsSession(int sessionId, IAutoFillManagerClient client, Handler handler,
-            ComponentName componentName, RemoteFillService.FillServiceCallbacks callbacks) {
-        mSessionId = sessionId;
-        mClient = client;
-        mHandler = handler;
-        mComponentName = componentName;
-        mCallbacks = callbacks;
-    }
-
-    void onFillRequest(int requestId, InlineSuggestionsRequest inlineRequest, int flags) {
-        final AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
-        final AtomicReference<AndroidFuture<FillResponse>> futureRef = new AtomicReference<>();
-        final AndroidFuture<FillResponse> fillRequest = new AndroidFuture<>();
-
-        mHandler.post(() -> {
-            if (sVerbose) {
-                Slog.v(TAG, "calling onFillRequest() for id=" + requestId);
-            }
-
-            try {
-                mClient.requestFillFromClient(requestId, inlineRequest,
-                        new FillCallbackImpl(fillRequest, futureRef, cancellationSink));
-            } catch (RemoteException e) {
-                fillRequest.completeExceptionally(e);
-            }
-        });
-
-        fillRequest.orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
-        futureRef.set(fillRequest);
-
-        synchronized (mLock) {
-            mPendingFillRequest = fillRequest;
-            mPendingFillRequestId = requestId;
-        }
-
-        fillRequest.whenComplete((res, err) -> mHandler.post(() -> {
-            synchronized (mLock) {
-                mPendingFillRequest = null;
-                mPendingFillRequestId = INVALID_REQUEST_ID;
-            }
-            if (err == null) {
-                processAutofillId(res);
-                mCallbacks.onFillRequestSuccess(requestId, res,
-                        mComponentName.getPackageName(), flags);
-            } else {
-                Slog.e(TAG, "Error calling on  client fill request", err);
-                if (err instanceof TimeoutException) {
-                    dispatchCancellationSignal(cancellationSink.get());
-                    mCallbacks.onFillRequestTimeout(requestId);
-                } else if (err instanceof CancellationException) {
-                    dispatchCancellationSignal(cancellationSink.get());
-                } else {
-                    mCallbacks.onFillRequestFailure(requestId, err.getMessage());
-                }
-            }
-        }));
-    }
-
-    /**
-     * Gets the application info for the component.
-     */
-    @Nullable
-    static ApplicationInfo getAppInfo(ComponentName comp, @UserIdInt int userId) {
-        try {
-            ApplicationInfo si = AppGlobals.getPackageManager().getApplicationInfo(
-                    comp.getPackageName(),
-                    PackageManager.GET_META_DATA,
-                    userId);
-            if (si != null) {
-                return si;
-            }
-        } catch (RemoteException e) {
-        }
-        return null;
-    }
-
-    /**
-     * Gets the user-visible name of the application.
-     */
-    @Nullable
-    @GuardedBy("mLock")
-    static CharSequence getAppLabelLocked(Context context, ApplicationInfo appInfo) {
-        return appInfo == null ? null : appInfo.loadSafeLabel(
-                context.getPackageManager(), 0 /* do not ellipsize */,
-                TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
-    }
-
-    /**
-     * Gets the user-visible icon of the application.
-     */
-    @Nullable
-    @GuardedBy("mLock")
-    static Drawable getAppIconLocked(Context context, ApplicationInfo appInfo) {
-        return appInfo == null ? null : appInfo.loadIcon(context.getPackageManager());
-    }
-
-    int cancelCurrentRequest() {
-        synchronized (mLock) {
-            return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
-                    ? mPendingFillRequestId
-                    : INVALID_REQUEST_ID;
-        }
-    }
-
-    /**
-     * The {@link AutofillId} which the client gets from its view is not contain the session id,
-     * but Autofill framework is using the {@link AutofillId} with a session id. So before using
-     * those ids in the Autofill framework, applies the current session id.
-     *
-     * @param res which response need to apply for a session id
-     */
-    private void processAutofillId(FillResponse res) {
-        if (res == null) {
-            return;
-        }
-
-        final List<Dataset> datasets = res.getDatasets();
-        if (datasets != null && !datasets.isEmpty()) {
-            for (int i = 0; i < datasets.size(); i++) {
-                final Dataset dataset = datasets.get(i);
-                if (dataset != null) {
-                    applySessionId(dataset.getFieldIds());
-                }
-            }
-        }
-
-        final SaveInfo saveInfo = res.getSaveInfo();
-        if (saveInfo != null) {
-            applySessionId(saveInfo.getOptionalIds());
-            applySessionId(saveInfo.getRequiredIds());
-            applySessionId(saveInfo.getSanitizerValues());
-            applySessionId(saveInfo.getTriggerId());
-        }
-    }
-
-    private void applySessionId(List<AutofillId> ids) {
-        if (ids == null || ids.isEmpty()) {
-            return;
-        }
-
-        for (int i = 0; i < ids.size(); i++) {
-            applySessionId(ids.get(i));
-        }
-    }
-
-    private void applySessionId(AutofillId[][] ids) {
-        if (ids == null) {
-            return;
-        }
-        for (int i = 0; i < ids.length; i++) {
-            applySessionId(ids[i]);
-        }
-    }
-
-    private void applySessionId(AutofillId[] ids) {
-        if (ids == null) {
-            return;
-        }
-        for (int i = 0; i < ids.length; i++) {
-            applySessionId(ids[i]);
-        }
-    }
-
-    private void applySessionId(AutofillId id) {
-        if (id == null) {
-            return;
-        }
-        id.setSessionId(mSessionId);
-    }
-
-    private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
-        if (signal == null) {
-            return;
-        }
-        try {
-            signal.cancel();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Error requesting a cancellation", e);
-        }
-    }
-
-    private class FillCallbackImpl extends IFillCallback.Stub {
-        final AndroidFuture<FillResponse> mFillRequest;
-        final AtomicReference<AndroidFuture<FillResponse>> mFutureRef;
-        final AtomicReference<ICancellationSignal> mCancellationSink;
-
-        FillCallbackImpl(AndroidFuture<FillResponse> fillRequest,
-                AtomicReference<AndroidFuture<FillResponse>> futureRef,
-                AtomicReference<ICancellationSignal> cancellationSink) {
-            mFillRequest = fillRequest;
-            mFutureRef = futureRef;
-            mCancellationSink = cancellationSink;
-        }
-
-        @Override
-        public void onCancellable(ICancellationSignal cancellation) {
-            AndroidFuture<FillResponse> future = mFutureRef.get();
-            if (future != null && future.isCancelled()) {
-                dispatchCancellationSignal(cancellation);
-            } else {
-                mCancellationSink.set(cancellation);
-            }
-        }
-
-        @Override
-        public void onSuccess(FillResponse response) {
-            mFillRequest.complete(response);
-        }
-
-        @Override
-        public void onFailure(int requestId, CharSequence message) {
-            String errorMessage = message == null ? "" : String.valueOf(message);
-            mFillRequest.completeExceptionally(
-                    new RuntimeException(errorMessage));
-        }
-    }
-}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f83d734..3736262 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -36,7 +36,6 @@
 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
 import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN;
-import static android.view.autofill.AutofillManager.FLAG_ENABLED_CLIENT_SUGGESTIONS;
 import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
 import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
 
@@ -446,9 +445,6 @@
      */
     private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl();
 
-    @Nullable
-    private ClientSuggestionsSession mClientSuggestionsSession;
-
     private final ClassificationState mClassificationState = new ClassificationState();
 
     // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
@@ -590,9 +586,6 @@
         /** Whether the current {@link FillResponse} is expired. */
         private boolean mExpiredResponse;
 
-        /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
-        private boolean mClientSuggestionsEnabled;
-
         /** Whether the fill dialog UI is disabled. */
         private boolean mFillDialogDisabled;
     }
@@ -623,21 +616,14 @@
                     }
                     mWaitForInlineRequest = inlineSuggestionsRequest != null;
                     mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
-                    mWaitForInlineRequest = inlineSuggestionsRequest != null;
-                    maybeRequestFillFromServiceLocked();
+                    maybeRequestFillLocked();
                     viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
                 }
             } : null;
         }
 
-        void newAutofillRequestLocked(@Nullable InlineSuggestionsRequest inlineRequest) {
-            mPendingFillRequest = null;
-            mWaitForInlineRequest = inlineRequest != null;
-            mPendingInlineSuggestionsRequest = inlineRequest;
-        }
-
         @GuardedBy("mLock")
-        void maybeRequestFillFromServiceLocked() {
+        void maybeRequestFillLocked() {
             if (mPendingFillRequest == null) {
                 return;
             }
@@ -647,15 +633,13 @@
                     return;
                 }
 
-                if (mPendingInlineSuggestionsRequest.isServiceSupported()) {
-                    mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
-                            mPendingFillRequest.getFillContexts(),
-                            mPendingFillRequest.getHints(),
-                            mPendingFillRequest.getClientState(),
-                            mPendingFillRequest.getFlags(),
-                            mPendingInlineSuggestionsRequest,
-                            mPendingFillRequest.getDelayedFillIntentSender());
-                }
+                mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
+                        mPendingFillRequest.getFillContexts(),
+                        mPendingFillRequest.getHints(),
+                        mPendingFillRequest.getClientState(),
+                        mPendingFillRequest.getFlags(),
+                        mPendingInlineSuggestionsRequest,
+                        mPendingFillRequest.getDelayedFillIntentSender());
             }
             mLastFillRequest = mPendingFillRequest;
 
@@ -777,7 +761,7 @@
                             : mDelayedFillPendingIntent.getIntentSender());
 
                 mPendingFillRequest = request;
-                maybeRequestFillFromServiceLocked();
+                maybeRequestFillLocked();
             }
 
             if (mActivityToken != null) {
@@ -1099,39 +1083,30 @@
     }
 
     /**
-     * Cancels the last request sent to the {@link #mRemoteFillService} or the
-     * {@link #mClientSuggestionsSession}.
+     * Cancels the last request sent to the {@link #mRemoteFillService}.
      */
     @GuardedBy("mLock")
     private void cancelCurrentRequestLocked() {
-        if (mRemoteFillService == null && mClientSuggestionsSession == null) {
-            wtf(null, "cancelCurrentRequestLocked() called without a remote service or a "
-                    + "client suggestions session.  mForAugmentedAutofillOnly: %s",
-                    mSessionFlags.mAugmentedAutofillOnly);
+        if (mRemoteFillService == null) {
+            wtf(null, "cancelCurrentRequestLocked() called without a remote service. "
+                + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
             return;
         }
+        final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
 
-        if (mRemoteFillService != null) {
-            final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
+        // Remove the FillContext as there will never be a response for the service
+        if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
+            final int numContexts = mContexts.size();
 
-            // Remove the FillContext as there will never be a response for the service
-            if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
-                final int numContexts = mContexts.size();
-
-                // It is most likely the last context, hence search backwards
-                for (int i = numContexts - 1; i >= 0; i--) {
-                    if (mContexts.get(i).getRequestId() == canceledRequest) {
-                        if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
-                        mContexts.remove(i);
-                        break;
-                    }
+            // It is most likely the last context, hence search backwards
+            for (int i = numContexts - 1; i >= 0; i--) {
+                if (mContexts.get(i).getRequestId() == canceledRequest) {
+                    if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
+                    mContexts.remove(i);
+                    break;
                 }
             }
         }
-
-        if (mClientSuggestionsSession != null) {
-            mClientSuggestionsSession.cancelCurrentRequest();
-        }
     }
 
     private boolean isViewFocusedLocked(int flags) {
@@ -1225,30 +1200,17 @@
             requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION);
         }
 
-        // Only ask IME to create inline suggestions request when
-        // 1. Autofill provider supports it or client enabled client suggestions.
-        // 2. The render service is available.
-        // 3. The view is focused. (The view may not be focused if the autofill is triggered
-        //    manually.)
+        // Only ask IME to create inline suggestions request if Autofill provider supports it and
+        // the render service is available except the autofill is triggered manually and the view
+        // is also not focused.
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
-        if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled)
-                && remoteRenderService != null
-                && (isViewFocusedLocked(flags) || (isRequestSupportFillDialog(flags)))) {
-            final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer;
-            if (mSessionFlags.mClientSuggestionsEnabled) {
-                final int finalRequestId = requestId;
-                inlineSuggestionsRequestConsumer = (inlineSuggestionsRequest) -> {
-                    // Using client suggestions
-                    synchronized (mLock) {
-                        onClientFillRequestLocked(finalRequestId, inlineSuggestionsRequest);
-                    }
-                    viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
-                };
-            } else {
-                inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked(
-                        viewState, /* isInlineRequest= */ true);
-            }
+        if (mSessionFlags.mInlineSupportedByService
+            && remoteRenderService != null
+            && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) {
+            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
+                mAssistReceiver.newAutofillRequestLocked(viewState,
+                    /* isInlineRequest= */ true);
             if (inlineSuggestionsRequestConsumer != null) {
                 final AutofillId focusedId = mCurrentViewId;
                 final int requestIdCopy = requestId;
@@ -1264,18 +1226,10 @@
                 );
                 viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
             }
-        } else if (mSessionFlags.mClientSuggestionsEnabled) {
-            // Request client suggestions for the dropdown mode
-            onClientFillRequestLocked(requestId, null);
         } else {
             mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false);
         }
 
-        if (mSessionFlags.mClientSuggestionsEnabled) {
-            // Using client suggestions, unnecessary request AssistStructure
-            return;
-        }
-
         // Now request the assist structure data.
         requestAssistStructureLocked(requestId, flags);
     }
@@ -1380,11 +1334,6 @@
             mSessionFlags = new SessionFlags();
             mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
             mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked();
-            if (mContext.checkCallingPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-                    == PackageManager.PERMISSION_GRANTED) {
-                mSessionFlags.mClientSuggestionsEnabled =
-                        (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0;
-            }
             setClientLocked(client);
         }
 
@@ -1522,15 +1471,14 @@
                 if (requestLog != null) {
                     requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1);
                 }
-                processNullResponseOrFallbackLocked(requestId, requestFlags);
+                processNullResponseLocked(requestId, requestFlags);
                 return;
             }
 
             // TODO: Check if this is required. We can still present datasets to the user even if
             //  traditional field classification is disabled.
             fieldClassificationIds = response.getFieldClassificationIds();
-            if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null
-                    && !mService.isFieldClassificationEnabledLocked()) {
+            if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
                 Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
                 processNullResponseLocked(requestId, requestFlags);
                 return;
@@ -1643,9 +1591,7 @@
                         || (ArrayUtils.isEmpty(saveInfo.getOptionalIds())
                             && ArrayUtils.isEmpty(saveInfo.getRequiredIds())
                             && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0)))
-                    && (ArrayUtils.isEmpty(response.getFieldClassificationIds())
-                        || (!mSessionFlags.mClientSuggestionsEnabled
-                        && !mService.isFieldClassificationEnabledLocked())));
+                    && (ArrayUtils.isEmpty(response.getFieldClassificationIds())));
         }
     }
 
@@ -1975,40 +1921,6 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void processNullResponseOrFallbackLocked(int requestId, int flags) {
-        if (!mSessionFlags.mClientSuggestionsEnabled) {
-            processNullResponseLocked(requestId, flags);
-            return;
-        }
-
-        // fallback to the default platform password manager
-        mSessionFlags.mClientSuggestionsEnabled = false;
-        mLastFillDialogTriggerIds = null;
-        // Log the existing FillResponse event.
-        mFillResponseEventLogger.logAndEndEvent();
-
-        final InlineSuggestionsRequest inlineRequest =
-                (mLastInlineSuggestionsRequest != null
-                        && mLastInlineSuggestionsRequest.first == requestId)
-                        ? mLastInlineSuggestionsRequest.second : null;
-
-        // Start a new FillRequest logger for client suggestion fallback.
-        mFillRequestEventLogger.startLogForNewRequest();
-        mRequestCount++;
-        mFillRequestEventLogger.maybeSetAppPackageUid(uid);
-        mFillRequestEventLogger.maybeSetFlags(
-            flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
-        mFillRequestEventLogger.maybeSetRequestTriggerReason(
-            TRIGGER_REASON_NORMAL_TRIGGER);
-        mFillRequestEventLogger.maybeSetIsClientSuggestionFallback(true);
-
-        mAssistReceiver.newAutofillRequestLocked(inlineRequest);
-        requestAssistStructureLocked(requestId,
-                flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
-        return;
-    }
-
     // FillServiceCallbacks
     @Override
     @SuppressWarnings("GuardedBy")
@@ -4205,22 +4117,13 @@
             filterText = value.getTextValue().toString();
         }
 
-        final CharSequence targetLabel;
-        final Drawable targetIcon;
-        synchronized (mLock) {
-            if (mSessionFlags.mClientSuggestionsEnabled) {
-                final ApplicationInfo appInfo = ClientSuggestionsSession.getAppInfo(mComponentName,
-                        mService.getUserId());
-                targetLabel = ClientSuggestionsSession.getAppLabelLocked(
-                        mService.getMaster().getContext(), appInfo);
-                targetIcon = ClientSuggestionsSession.getAppIconLocked(
-                        mService.getMaster().getContext(), appInfo);
-            } else {
-                targetLabel = mService.getServiceLabelLocked();
-                targetIcon = mService.getServiceIconLocked();
-            }
+        final CharSequence serviceLabel;
+        final Drawable serviceIcon;
+        synchronized (this.mService.mLock) {
+            serviceLabel = mService.getServiceLabelLocked();
+            serviceIcon = mService.getServiceIconLocked();
         }
-        if (targetLabel == null || targetIcon == null) {
+        if (serviceLabel == null || serviceIcon == null) {
             wtf(null, "onFillReady(): no service label or icon");
             return;
         }
@@ -4281,7 +4184,7 @@
 
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
-                targetLabel, targetIcon, this, mContext, id, mCompatMode);
+                serviceLabel, serviceIcon, this, mContext, id, mCompatMode);
 
         synchronized (mLock) {
             mPresentationStatsEventLogger.maybeSetCountShown(
@@ -4477,17 +4380,6 @@
             return false;
         }
 
-        final InlineSuggestionsRequest request = inlineSuggestionsRequest.get();
-        if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported()
-                || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) {
-            if (sDebug) {
-                Slog.d(TAG, "Inline suggestions not supported for "
-                        + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service")
-                        + ". Falling back to dropdown.");
-            }
-            return false;
-        }
-
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
         if (remoteRenderService == null) {
@@ -4502,8 +4394,8 @@
         }
 
         final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
-                new InlineFillUi.InlineFillUiInfo(request, focusedId,
-                        filterText, remoteRenderService, userId, id);
+            new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
+                filterText, remoteRenderService, userId, id);
         InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
                 new InlineFillUi.InlineSuggestionUiCallback() {
                     @Override
@@ -5154,26 +5046,6 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void onClientFillRequestLocked(int requestId,
-            InlineSuggestionsRequest inlineSuggestionsRequest) {
-        if (mClientSuggestionsSession == null) {
-            mClientSuggestionsSession = new ClientSuggestionsSession(id, mClient, mHandler,
-                    mComponentName, this);
-        }
-
-        if (mContexts == null) {
-            mContexts = new ArrayList<>(1);
-        }
-        mContexts.add(new FillContext(requestId, new AssistStructure(), mCurrentViewId));
-
-        if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) {
-            inlineSuggestionsRequest = null;
-        }
-
-        mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags);
-    }
-
     /**
      * The result of checking whether to show the save dialog, when session can be saved.
      *
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index d8fbd08..0172eaf 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -107,6 +107,9 @@
 import com.android.server.SystemService;
 import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
+import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
+import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
+import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
 import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
 import com.android.server.companion.transport.CompanionTransportManager;
 import com.android.server.pm.UserManagerInternal;
@@ -116,6 +119,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -199,6 +203,8 @@
     private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
             new RemoteCallbackList<>();
 
+    private CrossDeviceSyncController mCrossDeviceSyncController;
+
     public CompanionDeviceManagerService(Context context) {
         super(context);
 
@@ -238,6 +244,8 @@
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
+        // TODO(b/279663946): move context sync to a dedicated system service
+        mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
 
         // Publish "binder" service.
         final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl();
@@ -1353,6 +1361,39 @@
         public void removeInactiveSelfManagedAssociations() {
             CompanionDeviceManagerService.this.removeInactiveSelfManagedAssociations();
         }
+
+        @Override
+        public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback) {
+            if (CompanionDeviceConfig.isEnabled(
+                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                mCrossDeviceSyncController.registerCallMetadataSyncCallback(callback);
+            }
+        }
+
+        @Override
+        public void crossDeviceSync(int userId, Collection<CrossDeviceCall> calls) {
+            if (CompanionDeviceConfig.isEnabled(
+                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                mCrossDeviceSyncController.syncToAllDevicesForUserId(userId, calls);
+            }
+        }
+
+        @Override
+        public void crossDeviceSync(AssociationInfo associationInfo,
+                Collection<CrossDeviceCall> calls) {
+            if (CompanionDeviceConfig.isEnabled(
+                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                mCrossDeviceSyncController.syncToSingleDevice(associationInfo, calls);
+            }
+        }
+
+        @Override
+        public void sendCrossDeviceSyncMessage(int associationId, byte[] message) {
+            if (CompanionDeviceConfig.isEnabled(
+                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                mCrossDeviceSyncController.syncMessageToDevice(associationId, message);
+            }
+        }
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
index 3649240..3b108e6 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
@@ -16,12 +16,41 @@
 
 package com.android.server.companion;
 
+import android.companion.AssociationInfo;
+
+import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
+import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
+
+import java.util.Collection;
+
 /**
  * Companion Device Manager Local System Service Interface.
  */
-interface CompanionDeviceManagerServiceInternal {
+public interface CompanionDeviceManagerServiceInternal {
     /**
      * @see CompanionDeviceManagerService#removeInactiveSelfManagedAssociations
      */
     void removeInactiveSelfManagedAssociations();
+
+    /**
+     * Registers a callback from an InCallService / ConnectionService to CDM to process sync
+     * requests and perform call control actions.
+     */
+    void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback);
+
+    /**
+     * Requests a sync from an InCallService / ConnectionService to CDM, for the given association
+     * and message.
+     */
+    void sendCrossDeviceSyncMessage(int associationId, byte[] message);
+
+    /**
+     * Requests a sync from an InCallService to CDM, for the given user and call metadata.
+     */
+    void crossDeviceSync(int userId, Collection<CrossDeviceCall> calls);
+
+    /**
+     * Requests a sync from an InCallService to CDM, for the given association and call metadata.
+     */
+    void crossDeviceSync(AssociationInfo associationInfo, Collection<CrossDeviceCall> calls);
 }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
index ae4766a..443a732 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
@@ -17,15 +17,20 @@
 package com.android.server.companion.datatransfer.contextsync;
 
 import android.annotation.Nullable;
+import android.companion.AssociationInfo;
 import android.telecom.Call;
 import android.telecom.InCallService;
 import android.telecom.TelecomManager;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
 import com.android.server.companion.CompanionDeviceConfig;
+import com.android.server.companion.CompanionDeviceManagerServiceInternal;
 
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.stream.Collectors;
 
@@ -35,90 +40,132 @@
  */
 public class CallMetadataSyncInCallService extends InCallService {
 
+    private static final String TAG = "CallMetadataIcs";
     private static final long NOT_VALID = -1L;
 
+    private CompanionDeviceManagerServiceInternal mCdmsi;
+
     @VisibleForTesting
     final Map<Call, CrossDeviceCall> mCurrentCalls = new HashMap<>();
-    @VisibleForTesting
-    boolean mShouldSync;
+    @VisibleForTesting int mNumberOfActiveSyncAssociations;
     final Call.Callback mTelecomCallback = new Call.Callback() {
         @Override
         public void onDetailsChanged(Call call, Call.Details details) {
-            mCurrentCalls.get(call).updateCallDetails(details);
-        }
-    };
-    final CallMetadataSyncCallback mCallMetadataSyncCallback = new CallMetadataSyncCallback() {
-        @Override
-        void processCallControlAction(int crossDeviceCallId, int callControlAction) {
-            final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId,
-                    mCurrentCalls.values());
-            switch (callControlAction) {
-                case android.companion.Telecom.Call.ACCEPT:
-                    if (crossDeviceCall != null) {
-                        crossDeviceCall.doAccept();
-                    }
-                    break;
-                case android.companion.Telecom.Call.REJECT:
-                    if (crossDeviceCall != null) {
-                        crossDeviceCall.doReject();
-                    }
-                    break;
-                case android.companion.Telecom.Call.SILENCE:
-                    doSilence();
-                    break;
-                case android.companion.Telecom.Call.MUTE:
-                    doMute();
-                    break;
-                case android.companion.Telecom.Call.UNMUTE:
-                    doUnmute();
-                    break;
-                case android.companion.Telecom.Call.END:
-                    if (crossDeviceCall != null) {
-                        crossDeviceCall.doEnd();
-                    }
-                    break;
-                case android.companion.Telecom.Call.PUT_ON_HOLD:
-                    if (crossDeviceCall != null) {
-                        crossDeviceCall.doPutOnHold();
-                    }
-                    break;
-                case android.companion.Telecom.Call.TAKE_OFF_HOLD:
-                    if (crossDeviceCall != null) {
-                        crossDeviceCall.doTakeOffHold();
-                    }
-                    break;
-                default:
-            }
-        }
-
-        @Override
-        void requestCrossDeviceSync(int userId) {
-        }
-
-        @Override
-        void updateStatus(int userId, boolean shouldSyncCallMetadata) {
-            if (userId == getUserId()) {
-                mShouldSync = shouldSyncCallMetadata;
-                if (shouldSyncCallMetadata) {
-                    initializeCalls();
+            if (mNumberOfActiveSyncAssociations > 0) {
+                final CrossDeviceCall crossDeviceCall = mCurrentCalls.get(call);
+                if (crossDeviceCall != null) {
+                    crossDeviceCall.updateCallDetails(details);
+                    sync(getUserId());
                 } else {
-                    mCurrentCalls.clear();
+                    Slog.w(TAG, "Could not update details for nonexistent call");
                 }
             }
         }
     };
+    final CrossDeviceSyncControllerCallback
+            mCrossDeviceSyncControllerCallback = new CrossDeviceSyncControllerCallback() {
+                @Override
+                void processContextSyncMessage(int associationId,
+                        CallMetadataSyncData callMetadataSyncData) {
+                    final Iterator<CallMetadataSyncData.Call> iterator =
+                            callMetadataSyncData.getRequests().iterator();
+                    while (iterator.hasNext()) {
+                        final CallMetadataSyncData.Call call = iterator.next();
+                        if (call.getId() != 0) {
+                            // The call is already assigned an id; treat as control invocations.
+                            for (int control : call.getControls()) {
+                                processCallControlAction(call.getId(), control);
+                            }
+                        }
+                        iterator.remove();
+                    }
+                }
+
+                private void processCallControlAction(long crossDeviceCallId,
+                        int callControlAction) {
+                    final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId,
+                            mCurrentCalls.values());
+                    switch (callControlAction) {
+                        case android.companion.Telecom.Call.ACCEPT:
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doAccept();
+                            }
+                            break;
+                        case android.companion.Telecom.Call.REJECT:
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doReject();
+                            }
+                            break;
+                        case android.companion.Telecom.Call.SILENCE:
+                            doSilence();
+                            break;
+                        case android.companion.Telecom.Call.MUTE:
+                            doMute();
+                            break;
+                        case android.companion.Telecom.Call.UNMUTE:
+                            doUnmute();
+                            break;
+                        case android.companion.Telecom.Call.END:
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doEnd();
+                            }
+                            break;
+                        case android.companion.Telecom.Call.PUT_ON_HOLD:
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doPutOnHold();
+                            }
+                            break;
+                        case android.companion.Telecom.Call.TAKE_OFF_HOLD:
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doTakeOffHold();
+                            }
+                            break;
+                        default:
+                    }
+                }
+
+                @Override
+                void requestCrossDeviceSync(AssociationInfo associationInfo) {
+                    if (associationInfo.getUserId() == getUserId()) {
+                        sync(associationInfo);
+                    }
+                }
+
+                @Override
+                void updateNumberOfActiveSyncAssociations(int userId, boolean added) {
+                    if (userId == getUserId()) {
+                        final boolean wasActivelySyncing = mNumberOfActiveSyncAssociations > 0;
+                        if (added) {
+                            mNumberOfActiveSyncAssociations++;
+                        } else {
+                            mNumberOfActiveSyncAssociations--;
+                        }
+                        if (!wasActivelySyncing && mNumberOfActiveSyncAssociations > 0) {
+                            initializeCalls();
+                        } else if (wasActivelySyncing && mNumberOfActiveSyncAssociations <= 0) {
+                            mCurrentCalls.clear();
+                        }
+                    }
+                }
+    };
 
     @Override
     public void onCreate() {
         super.onCreate();
-        initializeCalls();
+        if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+            mCdmsi = LocalServices.getService(CompanionDeviceManagerServiceInternal.class);
+            mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback);
+        }
     }
 
     private void initializeCalls() {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
-                && mShouldSync) {
+                && mNumberOfActiveSyncAssociations > 0) {
             mCurrentCalls.putAll(getCalls().stream().collect(Collectors.toMap(call -> call,
                     call -> new CrossDeviceCall(getPackageManager(), call, getCallAudioState()))));
+            mCurrentCalls.keySet().forEach(call -> call.registerCallback(mTelecomCallback,
+                    getMainThreadHandler()));
+            sync(getUserId());
         }
     }
 
@@ -139,33 +186,39 @@
     @Override
     public void onCallAdded(Call call) {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
-                && mShouldSync) {
+                && mNumberOfActiveSyncAssociations > 0) {
             mCurrentCalls.put(call,
                     new CrossDeviceCall(getPackageManager(), call, getCallAudioState()));
+            call.registerCallback(mTelecomCallback);
+            sync(getUserId());
         }
     }
 
     @Override
     public void onCallRemoved(Call call) {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
-                && mShouldSync) {
+                && mNumberOfActiveSyncAssociations > 0) {
             mCurrentCalls.remove(call);
+            call.unregisterCallback(mTelecomCallback);
+            sync(getUserId());
         }
     }
 
     @Override
     public void onMuteStateChanged(boolean isMuted) {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
-                && mShouldSync) {
+                && mNumberOfActiveSyncAssociations > 0) {
             mCurrentCalls.values().forEach(call -> call.updateMuted(isMuted));
+            sync(getUserId());
         }
     }
 
     @Override
     public void onSilenceRinger() {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
-                && mShouldSync) {
+                && mNumberOfActiveSyncAssociations > 0) {
             mCurrentCalls.values().forEach(call -> call.updateSilencedIfRinging());
+            sync(getUserId());
         }
     }
 
@@ -183,4 +236,12 @@
             telecomManager.silenceRinger();
         }
     }
+
+    private void sync(int userId) {
+        mCdmsi.crossDeviceSync(userId, mCurrentCalls.values());
+    }
+
+    private void sync(AssociationInfo associationInfo) {
+        mCdmsi.crossDeviceSync(associationInfo, mCurrentCalls.values());
+    }
 }
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
index 3d8fb7a..adc5faf 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
@@ -16,27 +16,32 @@
 
 package com.android.server.companion.datatransfer.contextsync;
 
+import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_CONTEXT_SYNC;
+
 import android.app.admin.DevicePolicyManager;
 import android.companion.AssociationInfo;
 import android.companion.ContextSyncMessage;
+import android.companion.IOnMessageReceivedListener;
+import android.companion.IOnTransportsChangedListener;
 import android.companion.Telecom;
-import android.companion.Telecom.Call;
 import android.content.Context;
+import android.os.Binder;
 import android.os.UserHandle;
-import android.util.Pair;
 import android.util.Slog;
+import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoParseException;
+import android.util.proto.ProtoUtils;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.companion.CompanionDeviceConfig;
+import com.android.server.companion.transport.CompanionTransportManager;
+
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -45,149 +50,308 @@
 public class CrossDeviceSyncController {
 
     private static final String TAG = "CrossDeviceSyncController";
-    private static final int BYTE_ARRAY_SIZE = 64;
+
+    private static final int VERSION_1 = 1;
+    private static final int CURRENT_VERSION = VERSION_1;
 
     private final Context mContext;
-    private final Callback mCdmCallback;
-    private final Map<Integer, List<AssociationInfo>> mUserIdToAssociationInfo = new HashMap<>();
-    private final Map<Integer, Pair<InputStream, OutputStream>> mAssociationIdToStreams =
-            new HashMap<>();
+    private final CompanionTransportManager mCompanionTransportManager;
+    private final List<AssociationInfo> mConnectedAssociations = new ArrayList<>();
     private final Set<Integer> mBlocklist = new HashSet<>();
 
-    private CallMetadataSyncCallback mInCallServiceCallMetadataSyncCallback;
+    private CrossDeviceSyncControllerCallback mCrossDeviceSyncControllerCallback;
 
-    public CrossDeviceSyncController(Context context, Callback callback) {
+    public CrossDeviceSyncController(Context context,
+            CompanionTransportManager companionTransportManager) {
         mContext = context;
-        mCdmCallback = callback;
+        mCompanionTransportManager = companionTransportManager;
+        mCompanionTransportManager.addListener(new IOnTransportsChangedListener.Stub() {
+            @Override
+            public void onTransportsChanged(List<AssociationInfo> newAssociations) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    if (!CompanionDeviceConfig.isEnabled(
+                            CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                        return;
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                final List<AssociationInfo> existingAssociations = new ArrayList<>(
+                        mConnectedAssociations);
+                mConnectedAssociations.clear();
+                mConnectedAssociations.addAll(newAssociations);
+
+                if (mCrossDeviceSyncControllerCallback == null) {
+                    Slog.w(TAG, "No callback to report transports changed");
+                    return;
+                }
+                for (AssociationInfo associationInfo : newAssociations) {
+                    if (!existingAssociations.contains(associationInfo)
+                            && !isAssociationBlocked(associationInfo.getId())) {
+                        mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
+                                associationInfo.getUserId(), /* added= */ true);
+                        mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo);
+                    }
+                }
+                for (AssociationInfo associationInfo : existingAssociations) {
+                    if (!newAssociations.contains(associationInfo)) {
+                        if (isAssociationBlocked(associationInfo.getId())) {
+                            mBlocklist.remove(associationInfo.getId());
+                        } else {
+                            mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
+                                    associationInfo.getUserId(), /* added= */ false);
+                        }
+                    }
+                }
+            }
+        });
+        mCompanionTransportManager.addListener(MESSAGE_REQUEST_CONTEXT_SYNC,
+                new IOnMessageReceivedListener.Stub() {
+                    @Override
+                    public void onMessageReceived(int associationId, byte[] data) {
+                        if (mCrossDeviceSyncControllerCallback == null) {
+                            Slog.w(TAG, "No callback to process context sync message");
+                            return;
+                        }
+                        mCrossDeviceSyncControllerCallback.processContextSyncMessage(associationId,
+                                processTelecomDataFromSync(data));
+                    }
+                });
+    }
+
+    private boolean isAssociationBlocked(int associationId) {
+        return mBlocklist.contains(associationId);
     }
 
     /** Registers the call metadata callback. */
-    public void registerCallMetadataSyncCallback(CallMetadataSyncCallback callback) {
-        mInCallServiceCallMetadataSyncCallback = callback;
+    public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback) {
+        mCrossDeviceSyncControllerCallback = callback;
+        for (AssociationInfo associationInfo : mConnectedAssociations) {
+            if (!isAssociationBlocked(associationInfo.getId())) {
+                mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
+                        associationInfo.getUserId(), /* added= */ true);
+                mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo);
+            }
+        }
     }
 
     /** Allow specific associated devices to enable / disable syncing. */
     public void setSyncEnabled(AssociationInfo associationInfo, boolean enabled) {
         if (enabled) {
-            if (mBlocklist.contains(associationInfo.getId())) {
+            if (isAssociationBlocked(associationInfo.getId())) {
                 mBlocklist.remove(associationInfo.getId());
-                openChannel(associationInfo);
+                mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
+                        associationInfo.getUserId(), /* added= */ true);
+                mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo);
             }
         } else {
-            if (!mBlocklist.contains(associationInfo.getId())) {
+            if (!isAssociationBlocked(associationInfo.getId())) {
                 mBlocklist.add(associationInfo.getId());
-                closeChannel(associationInfo);
+                mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
+                        associationInfo.getUserId(), /* added= */ false);
+                // Send empty message to device to clear its data (otherwise it will get stale)
+                syncMessageToDevice(associationInfo.getId(), createEmptyMessage());
             }
         }
     }
 
-    /**
-     * Opens channels to newly associated devices, and closes channels to newly disassociated
-     * devices.
-     *
-     * TODO(b/265466098): this needs to be limited to just connected devices
-     */
-    public void onAssociationsChanged(int userId, List<AssociationInfo> newAssociationInfoList) {
-        final List<AssociationInfo> existingAssociationInfoList = mUserIdToAssociationInfo.get(
-                userId);
-        // Close channels to newly-disconnected devices.
-        for (AssociationInfo existingAssociationInfo : existingAssociationInfoList) {
-            if (!newAssociationInfoList.contains(existingAssociationInfo) && !mBlocklist.contains(
-                    existingAssociationInfo.getId())) {
-                closeChannel(existingAssociationInfo);
-            }
-        }
-        // Open channels to newly-connected devices.
-        for (AssociationInfo newAssociationInfo : newAssociationInfoList) {
-            if (!existingAssociationInfoList.contains(newAssociationInfo) && !mBlocklist.contains(
-                    newAssociationInfo.getId())) {
-                openChannel(newAssociationInfo);
-            }
-        }
-        mUserIdToAssociationInfo.put(userId, newAssociationInfoList);
-    }
-
     private boolean isAdminBlocked(int userId) {
         return mContext.getSystemService(DevicePolicyManager.class)
                 .getBluetoothContactSharingDisabled(UserHandle.of(userId));
     }
 
-    /** Stop reading, close streams, and close secure channel. */
-    private void closeChannel(AssociationInfo associationInfo) {
-        // TODO(b/265466098): stop reading from secure channel
-        final Pair<InputStream, OutputStream> streams = mAssociationIdToStreams.get(
-                associationInfo.getId());
-        if (streams != null) {
-            try {
-                if (streams.first != null) {
-                    streams.first.close();
-                }
-                if (streams.second != null) {
-                    streams.second.close();
-                }
-            } catch (IOException e) {
-                Slog.e(TAG, "Could not close streams for association " + associationInfo.getId(),
-                        e);
-            }
-        }
-        mCdmCallback.closeSecureChannel(associationInfo.getId());
-    }
-
-    /** Sync initial snapshot and start reading. */
-    private void openChannel(AssociationInfo associationInfo) {
-        final InputStream is = new ByteArrayInputStream(new byte[BYTE_ARRAY_SIZE]);
-        final OutputStream os = new ByteArrayOutputStream(BYTE_ARRAY_SIZE);
-        mAssociationIdToStreams.put(associationInfo.getId(), new Pair<>(is, os));
-        mCdmCallback.createSecureChannel(associationInfo.getId(), is, os);
-        // TODO(b/265466098): only requestSync for this specific association / connection?
-        mInCallServiceCallMetadataSyncCallback.requestCrossDeviceSync(associationInfo.getUserId());
-        // TODO(b/265466098): start reading from secure channel
-    }
-
     /**
      * Sync data to associated devices.
      *
      * @param userId The user whose data should be synced.
      * @param calls The full list of current calls for all users.
      */
-    public void crossDeviceSync(int userId, Collection<CrossDeviceCall> calls) {
-        final boolean isAdminBlocked = isAdminBlocked(userId);
-        for (AssociationInfo associationInfo : mUserIdToAssociationInfo.get(userId)) {
-            final Pair<InputStream, OutputStream> streams = mAssociationIdToStreams.get(
-                    associationInfo.getId());
-            final ProtoOutputStream pos = new ProtoOutputStream(streams.second);
-            final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
-            for (CrossDeviceCall call : calls) {
-                final long callsToken = pos.start(Telecom.CALLS);
-                pos.write(Call.ID, call.getId());
-                final long originToken = pos.start(Call.ORIGIN);
-                pos.write(Call.Origin.CALLER_ID, call.getReadableCallerId(isAdminBlocked));
-                pos.write(Call.Origin.APP_ICON, call.getCallingAppIcon());
-                pos.write(Call.Origin.APP_NAME, call.getCallingAppName());
-                pos.end(originToken);
-                pos.write(Call.STATUS, call.getStatus());
-                for (int control : call.getControls()) {
-                    pos.write(Call.CONTROLS_AVAILABLE, control);
-                }
-                pos.end(callsToken);
+    public void syncToAllDevicesForUserId(int userId, Collection<CrossDeviceCall> calls) {
+        final Set<Integer> associationIds = new HashSet<>();
+        for (AssociationInfo associationInfo : mConnectedAssociations) {
+            if (associationInfo.getUserId() == userId && !isAssociationBlocked(
+                    associationInfo.getId())) {
+                associationIds.add(associationInfo.getId());
             }
-            pos.end(telecomToken);
-            pos.flush();
         }
+        if (associationIds.isEmpty()) {
+            Slog.w(TAG, "No eligible devices to sync to");
+            return;
+        }
+
+        mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
+                createCallUpdateMessage(calls, userId),
+                associationIds.stream().mapToInt(Integer::intValue).toArray());
     }
 
     /**
-     * Callback to be implemented by CompanionDeviceManagerService.
+     * Sync data to associated devices.
+     *
+     * @param associationInfo The association whose data should be synced.
+     * @param calls           The full list of current calls for all users.
      */
-    public interface Callback {
-        /**
-         * Create a secure channel to send messages.
-         */
-        void createSecureChannel(int associationId, InputStream input, OutputStream output);
+    public void syncToSingleDevice(AssociationInfo associationInfo,
+            Collection<CrossDeviceCall> calls) {
+        if (isAssociationBlocked(associationInfo.getId())) {
+            Slog.e(TAG, "Cannot sync to requested device; connection is blocked");
+            return;
+        }
 
-        /**
-         * Close the secure channel created previously.
-         */
-        void closeSecureChannel(int associationId);
+        mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
+                createCallUpdateMessage(calls, associationInfo.getUserId()),
+                new int[]{associationInfo.getId()});
+    }
+
+    /**
+     * Sync data to associated devices.
+     *
+     * @param associationId   The association whose data should be synced.
+     * @param message         The message to sync.
+     */
+    public void syncMessageToDevice(int associationId, byte[] message) {
+        if (isAssociationBlocked(associationId)) {
+            Slog.e(TAG, "Cannot sync to requested device; connection is blocked");
+            return;
+        }
+
+        mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC, message,
+                new int[]{associationId});
+    }
+
+    @VisibleForTesting
+    CallMetadataSyncData processTelecomDataFromSync(byte[] data) {
+        final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+        final ProtoInputStream pis = new ProtoInputStream(data);
+        try {
+            int version = -1;
+            while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                switch (pis.getFieldNumber()) {
+                    case (int) ContextSyncMessage.VERSION:
+                        version = pis.readInt(ContextSyncMessage.VERSION);
+                        Slog.e(TAG, "Processing context sync message version " + version);
+                        break;
+                    case (int) ContextSyncMessage.TELECOM:
+                        if (version == VERSION_1) {
+                            final long telecomToken = pis.start(ContextSyncMessage.TELECOM);
+                            while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                                if (pis.getFieldNumber() == (int) Telecom.CALLS) {
+                                    final long callsToken = pis.start(Telecom.CALLS);
+                                    callMetadataSyncData.addCall(processCallDataFromSync(pis));
+                                    pis.end(callsToken);
+                                } else if (pis.getFieldNumber() == (int) Telecom.REQUESTS) {
+                                    final long requestsToken = pis.start(Telecom.REQUESTS);
+                                    callMetadataSyncData.addRequest(processCallDataFromSync(pis));
+                                    pis.end(requestsToken);
+                                } else {
+                                    Slog.e(TAG, "Unhandled field in Telecom:"
+                                            + ProtoUtils.currentFieldToString(pis));
+                                }
+                            }
+                            pis.end(telecomToken);
+                        } else {
+                            Slog.e(TAG, "Cannot process unsupported version " + version);
+                        }
+                        break;
+                    default:
+                        Slog.e(TAG, "Unhandled field in ContextSyncMessage:"
+                                + ProtoUtils.currentFieldToString(pis));
+                }
+            }
+        } catch (IOException | ProtoParseException e) {
+            throw new RuntimeException(e);
+        }
+        return callMetadataSyncData;
+    }
+
+    @VisibleForTesting
+    CallMetadataSyncData.Call processCallDataFromSync(ProtoInputStream pis) throws IOException {
+        final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pis.getFieldNumber()) {
+                case (int) Telecom.Call.ID:
+                    call.setId(pis.readLong(Telecom.Call.ID));
+                    break;
+                case (int) Telecom.Call.ORIGIN:
+                    final long originToken = pis.start(Telecom.Call.ORIGIN);
+                    while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                        switch (pis.getFieldNumber()) {
+                            case (int) Telecom.Call.Origin.APP_ICON:
+                                call.setAppIcon(pis.readBytes(Telecom.Call.Origin.APP_ICON));
+                                break;
+                            case (int) Telecom.Call.Origin.APP_NAME:
+                                call.setAppName(pis.readString(Telecom.Call.Origin.APP_NAME));
+                                break;
+                            case (int) Telecom.Call.Origin.CALLER_ID:
+                                call.setCallerId(pis.readString(Telecom.Call.Origin.CALLER_ID));
+                                break;
+                            case (int) Telecom.Call.Origin.APP_IDENTIFIER:
+                                call.setAppIdentifier(
+                                        pis.readString(Telecom.Call.Origin.APP_IDENTIFIER));
+                                break;
+                            default:
+                                Slog.e(TAG, "Unhandled field in Origin:"
+                                        + ProtoUtils.currentFieldToString(pis));
+                        }
+                    }
+                    pis.end(originToken);
+                    break;
+                case (int) Telecom.Call.STATUS:
+                    call.setStatus(pis.readInt(Telecom.Call.STATUS));
+                    break;
+                case (int) Telecom.Call.CONTROLS:
+                    call.addControl(pis.readInt(Telecom.Call.CONTROLS));
+                    break;
+                default:
+                    Slog.e(TAG,
+                            "Unhandled field in Telecom:" + ProtoUtils.currentFieldToString(pis));
+            }
+        }
+        return call;
+    }
+
+    @VisibleForTesting
+    byte[] createCallUpdateMessage(Collection<CrossDeviceCall> calls, int userId) {
+        final ProtoOutputStream pos = new ProtoOutputStream();
+        pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
+        final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
+        for (CrossDeviceCall call : calls) {
+            final long callsToken = pos.start(Telecom.CALLS);
+            pos.write(Telecom.Call.ID, call.getId());
+            final long originToken = pos.start(Telecom.Call.ORIGIN);
+            pos.write(Telecom.Call.Origin.CALLER_ID,
+                    call.getReadableCallerId(isAdminBlocked(userId)));
+            pos.write(Telecom.Call.Origin.APP_ICON, call.getCallingAppIcon());
+            pos.write(Telecom.Call.Origin.APP_NAME, call.getCallingAppName());
+            pos.write(Telecom.Call.Origin.APP_IDENTIFIER, call.getCallingAppPackageName());
+            pos.end(originToken);
+            pos.write(Telecom.Call.STATUS, call.getStatus());
+            for (int control : call.getControls()) {
+                pos.write(Telecom.Call.CONTROLS, control);
+            }
+            pos.end(callsToken);
+        }
+        pos.end(telecomToken);
+        return pos.getBytes();
+    }
+
+    /** Create a call control message. */
+    public static byte[] createCallControlMessage(long callId, int control) {
+        final ProtoOutputStream pos = new ProtoOutputStream();
+        pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
+        final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
+        final long requestsToken = pos.start(Telecom.REQUESTS);
+        pos.write(Telecom.Call.ID, callId);
+        pos.write(Telecom.Call.CONTROLS, control);
+        pos.end(requestsToken);
+        pos.end(telecomToken);
+        return pos.getBytes();
+    }
+
+    /** Create an empty context sync message, used to clear state. */
+    public static byte[] createEmptyMessage() {
+        final ProtoOutputStream pos = new ProtoOutputStream();
+        pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
+        return pos.getBytes();
     }
 }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
similarity index 67%
rename from services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
rename to services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
index 7c339d2..31e10a8 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
@@ -16,12 +16,14 @@
 
 package com.android.server.companion.datatransfer.contextsync;
 
+import android.companion.AssociationInfo;
+
 /** Callback for call metadata syncing. */
-public abstract class CallMetadataSyncCallback {
+public abstract class CrossDeviceSyncControllerCallback {
 
-    abstract void processCallControlAction(int crossDeviceCallId, int callControlAction);
+    void processContextSyncMessage(int associationId, CallMetadataSyncData callMetadataSyncData) {}
 
-    abstract void requestCrossDeviceSync(int userId);
+    void requestCrossDeviceSync(AssociationInfo associationInfo) {}
 
-    abstract void updateStatus(int userId, boolean shouldSyncCallMetadata);
+    void updateNumberOfActiveSyncAssociations(int userId, boolean added) {}
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 678d582..1f1f0e9 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -52,6 +52,7 @@
 import static android.os.PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_STARTER;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
+import static android.os.PowerExemptionManager.REASON_ALARM_MANAGER_ALARM_CLOCK;
 import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
 import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
 import static android.os.PowerExemptionManager.REASON_BACKGROUND_FGS_PERMISSION;
@@ -458,11 +459,12 @@
         public void updateBackgroundRestrictedForUidPackage(int uid, String packageName,
                 boolean restricted) {
             synchronized (mAm) {
-                if (!isForegroundServiceAllowedInBackgroundRestricted(uid, packageName)) {
-                    stopAllForegroundServicesLocked(uid, packageName);
-                }
                 mAm.mProcessList.updateBackgroundRestrictedForUidPackageLocked(
                         uid, packageName, restricted);
+                if (!isForegroundServiceAllowedInBackgroundRestricted(uid, packageName)
+                        && !isTempAllowedByAlarmClock(uid)) {
+                    stopAllForegroundServicesLocked(uid, packageName);
+                }
             }
         }
     }
@@ -475,7 +477,11 @@
             final ServiceRecord r = smap.mServicesByInstanceName.valueAt(i);
             if (uid == r.serviceInfo.applicationInfo.uid
                     || packageName.equals(r.serviceInfo.packageName)) {
-                if (r.isForeground) {
+                // If the FGS is started by temp allowlist of alarm-clock
+                // (REASON_ALARM_MANAGER_ALARM_CLOCK), allow it to continue and do not stop it,
+                // even the app is background-restricted.
+                if (r.isForeground
+                        && r.mAllowStartForegroundAtEntering != REASON_ALARM_MANAGER_ALARM_CLOCK) {
                     toStop.add(r);
                 }
             }
@@ -762,6 +768,15 @@
         }
     }
 
+    private static void traceInstant(@NonNull String message, @NonNull ServiceRecord service) {
+        if (!Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            return;
+        }
+        final String serviceName = (service.getComponentName() != null)
+                ? service.getComponentName().toShortString() : "(?)";
+        Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, message + serviceName);
+    }
+
     ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
             int callingPid, int callingUid, boolean fgRequired, String callingPackage,
             @Nullable String callingFeatureId, final int userId, boolean isSdkSandboxService,
@@ -818,6 +833,9 @@
         }
 
         ServiceRecord r = res.record;
+
+        traceInstant("startService(): ", r);
+
         // Note, when startService() or startForegroundService() is called on an already
         // running SHORT_SERVICE FGS, the call will succeed (i.e. we won't throw
         // ForegroundServiceStartNotAllowedException), even when the service is already timed
@@ -860,7 +878,9 @@
         // start analogously to the legacy-app forced-restrictions case, regardless
         // of its target SDK version.
         boolean forcedStandby = false;
-        if (bgLaunch && appRestrictedAnyInBackground(appUid, appPackageName)) {
+        if (bgLaunch
+                && appRestrictedAnyInBackground(appUid, appPackageName)
+                && !isTempAllowedByAlarmClock(appUid)) {
             if (DEBUG_FOREGROUND_SERVICE) {
                 Slog.d(TAG, "Forcing bg-only service start only for " + r.shortInstanceName
                         + " : bgLaunch=" + bgLaunch + " callerFg=" + callerFg);
@@ -1407,6 +1427,7 @@
     }
 
     private void stopServiceLocked(ServiceRecord service, boolean enqueueOomAdj) {
+        traceInstant("stopService(): ", service);
         try {
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "stopServiceLocked()");
             if (service.delayed) {
@@ -1918,6 +1939,20 @@
                 && isForegroundServiceAllowedInBackgroundRestricted(app);
     }
 
+    /*
+     * If the FGS start is temp allowlisted by alarm-clock(REASON_ALARM_MANAGER_ALARM_CLOCK), it is
+     * allowed even the app is background-restricted.
+     */
+    private boolean isTempAllowedByAlarmClock(int uid) {
+        final ActivityManagerService.FgsTempAllowListItem item =
+                mAm.isAllowlistedForFgsStartLOSP(uid);
+        if (item != null) {
+            return item.mReasonCode == REASON_ALARM_MANAGER_ALARM_CLOCK;
+        } else {
+            return false;
+        }
+    }
+
     void logFgsApiBeginLocked(int uid, int pid, int apiType) {
         synchronized (mFGSLogger) {
             mFGSLogger.logForegroundServiceApiEventBegin(uid, pid, apiType, "");
@@ -1946,6 +1981,7 @@
             if (notification == null) {
                 throw new IllegalArgumentException("null notification");
             }
+            traceInstant("startForeground(): ", r);
             final int foregroundServiceStartType = foregroundServiceType;
             // Instant apps need permission to create foreground services.
             if (r.appInfo.isInstantApp()) {
@@ -2050,7 +2086,8 @@
                 // Apps that are TOP or effectively similar may call startForeground() on
                 // their services even if they are restricted from doing that while in bg.
                 if (!ignoreForeground
-                        && !isForegroundServiceAllowedInBackgroundRestricted(r.app)) {
+                        && !isForegroundServiceAllowedInBackgroundRestricted(r.app)
+                        && !isTempAllowedByAlarmClock(r.app.uid)) {
                     Slog.w(TAG,
                             "Service.startForeground() not allowed due to bg restriction: service "
                                     + r.shortInstanceName);
@@ -2484,6 +2521,7 @@
             }
         } else {
             if (r.isForeground) {
+                traceInstant("stopForeground(): ", r);
                 final ServiceMap smap = getServiceMapLocked(r.userId);
                 if (smap != null) {
                     decActiveForegroundAppLocked(smap, r);
@@ -3311,6 +3349,7 @@
                     Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
                 }
             }
+            traceInstant("short FGS start/extend: ", sr);
             sr.setShortFgsInfo(SystemClock.uptimeMillis());
 
             // We'll restart the timeout.
@@ -3356,10 +3395,11 @@
                 return;
             }
             Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
-            final long now = SystemClock.uptimeMillis();
+            traceInstant("short FGS timeout: ", sr);
+
             logFGSStateChangeLocked(sr,
                     FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
-                    now > sr.mFgsEnterTime ? (int) (now - sr.mFgsEnterTime) : 0,
+                    nowUptime > sr.mFgsEnterTime ? (int) (nowUptime - sr.mFgsEnterTime) : 0,
                     FGS_STOP_REASON_UNKNOWN,
                     FGS_TYPE_POLICY_CHECK_UNKNOWN);
             try {
@@ -3410,6 +3450,7 @@
             }
 
             Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr);
+            traceInstant("short FGS demote: ", sr);
 
             mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
         }
@@ -3440,6 +3481,9 @@
             } else {
                 Slog.e(TAG_SERVICE, message);
             }
+
+            traceInstant("short FGS ANR: ", sr);
+
             mAm.appNotResponding(sr.app, tr);
 
             // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a54e8e9..1a5d425 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5190,7 +5190,10 @@
                         throw new IllegalArgumentException(
                                 "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
                     }
-                    if (PendingIntent.isNewMutableDisallowedImplicitPendingIntent(flags, intent)) {
+                    boolean isActivityResultType =
+                            type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT;
+                    if (PendingIntent.isNewMutableDisallowedImplicitPendingIntent(flags, intent,
+                            isActivityResultType)) {
                         boolean isChangeEnabled = CompatChanges.isChangeEnabled(
                                         PendingIntent.BLOCK_MUTABLE_IMPLICIT_PENDING_INTENT,
                                         owningUid);
@@ -6915,7 +6918,7 @@
         mActivityTaskManager.unhandledBack();
     }
 
-    // TODO: Move to ContentProviderHelper?
+    // TODO: Replace this method with one that returns a bound IContentProvider.
     public ParcelFileDescriptor openContentUri(String uriString) throws RemoteException {
         enforceNotIsolatedCaller("openContentUri");
         final int userId = UserHandle.getCallingUserId();
@@ -6944,6 +6947,16 @@
                     Log.e(TAG, "Cannot find package for uid: " + uid);
                     return null;
                 }
+
+                final ApplicationInfo appInfo = mPackageManagerInt.getApplicationInfo(
+                        androidPackage.getPackageName(), /*flags*/0, Process.SYSTEM_UID,
+                        UserHandle.USER_SYSTEM);
+                if (!appInfo.isVendor() && !appInfo.isSystemApp() && !appInfo.isSystemExt()
+                        && !appInfo.isProduct()) {
+                    Log.e(TAG, "openContentUri may only be used by vendor/system/product.");
+                    return null;
+                }
+
                 final AttributionSource attributionSource = new AttributionSource(
                         Binder.getCallingUid(), androidPackage.getPackageName(), null);
                 pfd = cph.provider.openFile(attributionSource, uri, "r", null);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 1426cfd..3bc5de9 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -2100,9 +2100,12 @@
             final boolean frozen;
             final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
 
-            opt.setPendingFreeze(false);
-
             synchronized (mProcLock) {
+                // someone has canceled this freeze
+                if (!opt.isPendingFreeze()) {
+                    return;
+                }
+                opt.setPendingFreeze(false);
                 pid = proc.getPid();
 
                 if (mFreezerOverride) {
@@ -2148,7 +2151,6 @@
                 try {
                     traceAppFreeze(proc.processName, pid, -1);
                     Process.setProcessFrozen(pid, proc.uid, true);
-
                     opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
                     opt.setFrozen(true);
                     opt.setHasCollectedFrozenPSS(false);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 78aafeb..6551db9 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -643,6 +643,7 @@
 
         pw.print(prefix); pw.print("lastUntrustedSetFgsRestrictionAllowedTime=");
         TimeUtils.formatDuration(mLastUntrustedSetFgsRestrictionAllowedTime, now, pw);
+        pw.println();
 
         if (delayed) {
             pw.print(prefix); pw.print("delayed="); pw.println(delayed);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a181402..b2fdee7 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -3747,7 +3747,8 @@
             synchronized (mUserSwitchingDialogLock) {
                 dismissUserSwitchingDialog(null);
                 mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser,
-                        switchingFromSystemUserMessage, switchingToSystemUserMessage);
+                        switchingFromSystemUserMessage, switchingToSystemUserMessage,
+                        getWindowManager());
                 mUserSwitchingDialog.show(onShown);
             }
         }
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 649305f..412fbe79 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.pm.UserInfo;
@@ -38,6 +37,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.util.Slog;
 import android.util.TypedValue;
 import android.view.View;
@@ -51,6 +51,7 @@
 import com.android.internal.R;
 import com.android.internal.util.ObjectUtils;
 import com.android.internal.util.UserIcons;
+import com.android.server.wm.WindowManagerService;
 
 /**
  * Dialog to show during the user switch. This dialog shows target user's name and their profile
@@ -70,11 +71,14 @@
     protected final UserInfo mNewUser;
     private final String mSwitchingFromSystemUserMessage;
     private final String mSwitchingToSystemUserMessage;
+    private final WindowManagerService mWindowManager;
     protected final Context mContext;
     private final int mTraceCookie;
+    private final boolean mNeedToFreezeScreen;
 
     UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser,
-            String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
+            String switchingFromSystemUserMessage, String switchingToSystemUserMessage,
+            WindowManagerService windowManager) {
         // TODO(b/278857848): Make full screen user switcher cover top part of the screen as well.
         //                    This problem is seen only on phones, it works fine on tablets.
         super(context, R.style.Theme_Material_NoActionBar_Fullscreen);
@@ -84,8 +88,10 @@
         mNewUser = newUser;
         mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
         mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
-        mDisableAnimations = ActivityManager.isLowRamDeviceStatic() || SystemProperties.getBoolean(
+        mDisableAnimations = SystemProperties.getBoolean(
                 "debug.usercontroller.disable_user_switching_dialog_animations", false);
+        mWindowManager = windowManager;
+        mNeedToFreezeScreen = !mDisableAnimations && !isUserSetupComplete(newUser);
         mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id;
 
         inflateContent();
@@ -167,6 +173,11 @@
                 : res.getString(R.string.user_switching_message, mNewUser.name);
     }
 
+    private boolean isUserSetupComplete(UserInfo user) {
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, /* default= */ 0, user.id) == 1;
+    }
+
     @Override
     public void show() {
         asyncTraceBegin("", 0);
@@ -176,29 +187,24 @@
     @Override
     public void dismiss() {
         super.dismiss();
+        stopFreezingScreen();
         asyncTraceEnd("", 0);
     }
 
     public void show(@NonNull Runnable onShown) {
         if (DEBUG) Slog.d(TAG, "show called");
         show();
-
-        if (mDisableAnimations) {
+        startShowAnimation(() -> {
+            startFreezingScreen();
             onShown.run();
-        } else {
-            startShowAnimation(onShown);
-        }
+        });
     }
 
     public void dismiss(@Nullable Runnable onDismissed) {
         if (DEBUG) Slog.d(TAG, "dismiss called");
-
         if (onDismissed == null) {
             // no animation needed
             dismiss();
-        } else if (mDisableAnimations) {
-            dismiss();
-            onDismissed.run();
         } else {
             startDismissAnimation(() -> {
                 dismiss();
@@ -207,7 +213,31 @@
         }
     }
 
+    private void startFreezingScreen() {
+        if (!mNeedToFreezeScreen) {
+            return;
+        }
+        if (DEBUG) Slog.d(TAG, "startFreezingScreen");
+        Trace.traceBegin(TRACE_TAG, "startFreezingScreen");
+        mWindowManager.startFreezingScreen(0, 0);
+        Trace.traceEnd(TRACE_TAG);
+    }
+
+    private void stopFreezingScreen() {
+        if (!mNeedToFreezeScreen) {
+            return;
+        }
+        if (DEBUG) Slog.d(TAG, "stopFreezingScreen");
+        Trace.traceBegin(TRACE_TAG, "stopFreezingScreen");
+        mWindowManager.stopFreezingScreen();
+        Trace.traceEnd(TRACE_TAG);
+    }
+
     private void startShowAnimation(Runnable onAnimationEnd) {
+        if (mDisableAnimations) {
+            onAnimationEnd.run();
+            return;
+        }
         asyncTraceBegin("-showAnimation", 1);
         startDialogAnimation(new AlphaAnimation(0, 1), () -> {
             asyncTraceEnd("-showAnimation", 1);
@@ -222,6 +252,11 @@
     }
 
     private void startDismissAnimation(Runnable onAnimationEnd) {
+        if (mDisableAnimations || mNeedToFreezeScreen) {
+            // animations are disabled or screen is frozen, no need to play an animation
+            onAnimationEnd.run();
+            return;
+        }
         asyncTraceBegin("-dismissAnimation", 3);
         startDialogAnimation(new AlphaAnimation(1, 0), () -> {
             asyncTraceEnd("-dismissAnimation", 3);
@@ -231,8 +266,11 @@
     }
 
     private void startProgressAnimation(Runnable onAnimationEnd) {
-        final ImageView progressCircular = findViewById(R.id.progress_circular);
-        final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) progressCircular.getDrawable();
+        final AnimatedVectorDrawable avd = getSpinnerAVD();
+        if (mDisableAnimations || avd == null) {
+            onAnimationEnd.run();
+            return;
+        }
         avd.registerAnimationCallback(new Animatable2.AnimationCallback() {
             @Override
             public void onAnimationEnd(Drawable drawable) {
@@ -242,7 +280,23 @@
         avd.start();
     }
 
+    private AnimatedVectorDrawable getSpinnerAVD() {
+        final ImageView view = findViewById(R.id.progress_circular);
+        if (view != null) {
+            final Drawable drawable = view.getDrawable();
+            if (drawable instanceof AnimatedVectorDrawable) {
+                return (AnimatedVectorDrawable) drawable;
+            }
+        }
+        return null;
+    }
+
     private void startDialogAnimation(Animation animation, Runnable onAnimationEnd) {
+        final View view = findViewById(R.id.content);
+        if (mDisableAnimations || view == null) {
+            onAnimationEnd.run();
+            return;
+        }
         animation.setDuration(DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS);
         animation.setAnimationListener(new Animation.AnimationListener() {
             @Override
@@ -260,7 +314,7 @@
 
             }
         });
-        findViewById(R.id.content).startAnimation(animation);
+        view.startAnimation(animation);
     }
 
     private void asyncTraceBegin(String subTag, int subCookie) {
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 1f82961..6d43061 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -41,6 +41,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.RingBuffer;
@@ -278,6 +279,11 @@
         }
     }
 
+    private boolean hasWifiTransport(Network network) {
+        final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
+        return nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
+    }
+
     @Override
     public synchronized void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader,
             byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs) {
@@ -286,12 +292,21 @@
             throw new IllegalArgumentException("Prefix " + prefix
                     + " required in format <nethandle>:<interface>");
         }
+        final long netHandle = Long.parseLong(prefixParts[0]);
+        final Network network = Network.fromNetworkHandle(netHandle);
 
         final WakeupEvent event = new WakeupEvent();
         event.iface = prefixParts[1];
         event.uid = uid;
         event.ethertype = ethertype;
-        event.dstHwAddr = MacAddress.fromBytes(dstHw);
+        if (ArrayUtils.isEmpty(dstHw)) {
+            if (hasWifiTransport(network)) {
+                Log.e(TAG, "Empty mac address on WiFi transport, network: " + network);
+            }
+            event.dstHwAddr = null;
+        } else {
+            event.dstHwAddr = MacAddress.fromBytes(dstHw);
+        }
         event.srcIp = srcIp;
         event.dstIp = dstIp;
         event.ipNextHeader = ipNextHeader;
@@ -306,14 +321,12 @@
 
         final BatteryStatsInternal bsi = LocalServices.getService(BatteryStatsInternal.class);
         if (bsi != null) {
-            final long netHandle = Long.parseLong(prefixParts[0]);
             final long elapsedMs = SystemClock.elapsedRealtime() + event.timestampMs
                     - System.currentTimeMillis();
-            bsi.noteCpuWakingNetworkPacket(Network.fromNetworkHandle(netHandle), elapsedMs,
-                    event.uid);
+            bsi.noteCpuWakingNetworkPacket(network, elapsedMs, event.uid);
         }
 
-        final String dstMac = event.dstHwAddr.toString();
+        final String dstMac = String.valueOf(event.dstHwAddr);
         FrameworkStatsLog.write(FrameworkStatsLog.PACKET_WAKEUP_OCCURRED,
                 uid, event.iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort);
     }
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index fd94be9..17f928a 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -65,7 +65,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
-import com.android.internal.display.RefreshRateSettingsUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.display.DisplayDeviceConfig;
@@ -1067,10 +1066,10 @@
 
     @VisibleForTesting
     final class SettingsObserver extends ContentObserver {
-        private final Uri mSmoothDisplaySetting =
-                Settings.System.getUriFor(Settings.System.SMOOTH_DISPLAY);
-        private final Uri mForcePeakRefreshRateSetting =
-                Settings.System.getUriFor(Settings.System.FORCE_PEAK_REFRESH_RATE);
+        private final Uri mPeakRefreshRateSetting =
+                Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
+        private final Uri mMinRefreshRateSetting =
+                Settings.System.getUriFor(Settings.System.MIN_REFRESH_RATE);
         private final Uri mLowPowerModeSetting =
                 Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE);
         private final Uri mMatchContentFrameRateSetting =
@@ -1106,8 +1105,9 @@
 
         public void observe() {
             final ContentResolver cr = mContext.getContentResolver();
-            mInjector.registerSmoothDisplayObserver(cr, this);
-            mInjector.registerForcePeakRefreshRateObserver(cr, this);
+            mInjector.registerPeakRefreshRateObserver(cr, this);
+            cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this,
+                    UserHandle.USER_SYSTEM);
             cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
                     UserHandle.USER_SYSTEM);
             cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
@@ -1149,8 +1149,8 @@
         @Override
         public void onChange(boolean selfChange, Uri uri, int userId) {
             synchronized (mLock) {
-                if (mSmoothDisplaySetting.equals(uri)
-                        || mForcePeakRefreshRateSetting.equals(uri)) {
+                if (mPeakRefreshRateSetting.equals(uri)
+                        || mMinRefreshRateSetting.equals(uri)) {
                     updateRefreshRateSettingLocked();
                 } else if (mLowPowerModeSetting.equals(uri)) {
                     updateLowPowerModeSettingLocked();
@@ -1205,9 +1205,12 @@
         }
 
         private void updateRefreshRateSettingLocked() {
-            updateRefreshRateSettingLocked(RefreshRateSettingsUtils.getMinRefreshRate(mContext),
-                    RefreshRateSettingsUtils.getPeakRefreshRate(mContext, mDefaultPeakRefreshRate),
-                    mDefaultRefreshRate);
+            final ContentResolver cr = mContext.getContentResolver();
+            float minRefreshRate = Settings.System.getFloatForUser(cr,
+                    Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId());
+            float peakRefreshRate = Settings.System.getFloatForUser(cr,
+                    Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate, cr.getUserId());
+            updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
         }
 
         private void updateRefreshRateSettingLocked(
@@ -2840,17 +2843,12 @@
     }
 
     interface Injector {
-        Uri SMOOTH_DISPLAY_URI = Settings.System.getUriFor(Settings.System.SMOOTH_DISPLAY);
-        Uri FORCE_PEAK_REFRESH_RATE_URI =
-                Settings.System.getUriFor(Settings.System.FORCE_PEAK_REFRESH_RATE);
+        Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
 
         @NonNull
         DeviceConfigInterface getDeviceConfig();
 
-        void registerSmoothDisplayObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer);
-
-        void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr,
+        void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer);
 
         void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
@@ -2890,16 +2888,9 @@
         }
 
         @Override
-        public void registerSmoothDisplayObserver(@NonNull ContentResolver cr,
+        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer) {
-            cr.registerContentObserver(SMOOTH_DISPLAY_URI, false /*notifyDescendants*/,
-                    observer, UserHandle.USER_SYSTEM);
-        }
-
-        @Override
-        public void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer) {
-            cr.registerContentObserver(FORCE_PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
+            cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
                     observer, UserHandle.USER_SYSTEM);
         }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index 220a438..b82129b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -35,6 +35,7 @@
             ERROR_DESTINATION,
             ERROR_PARAMETER,
             ERROR_PARAMETER_SHORT,
+            ERROR_PARAMETER_LONG,
     })
     public @interface ValidationResult {};
 
@@ -43,6 +44,7 @@
     static final int ERROR_DESTINATION = 2;
     static final int ERROR_PARAMETER = 3;
     static final int ERROR_PARAMETER_SHORT = 4;
+    static final int ERROR_PARAMETER_LONG = 5;
 
     interface ParameterValidator {
         /**
@@ -159,11 +161,13 @@
         addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE,
                 new AsciiValidator(3), DEST_BROADCAST);
 
-        ParameterValidator statusRequestValidator = new OneByteRangeValidator(0x01, 0x03);
+        ParameterValidator statusRequestValidator = new MinimumOneByteRangeValidator(0x01, 0x03);
         addValidationInfo(
-                Constants.MESSAGE_DECK_CONTROL, new OneByteRangeValidator(0x01, 0x04), DEST_DIRECT);
+                Constants.MESSAGE_DECK_CONTROL,
+                        new MinimumOneByteRangeValidator(0x01, 0x04), DEST_DIRECT);
         addValidationInfo(
-                Constants.MESSAGE_DECK_STATUS, new OneByteRangeValidator(0x11, 0x1F), DEST_DIRECT);
+                Constants.MESSAGE_DECK_STATUS,
+                        new MinimumOneByteRangeValidator(0x11, 0x1F), DEST_DIRECT);
         addValidationInfo(Constants.MESSAGE_GIVE_DECK_STATUS, statusRequestValidator, DEST_DIRECT);
         addValidationInfo(Constants.MESSAGE_PLAY, new PlayModeValidator(), DEST_DIRECT);
 
@@ -201,9 +205,11 @@
 
         // Messages for the Device Menu Control.
         addValidationInfo(
-                Constants.MESSAGE_MENU_REQUEST, new OneByteRangeValidator(0x00, 0x02), DEST_DIRECT);
+                Constants.MESSAGE_MENU_REQUEST,
+                        new MinimumOneByteRangeValidator(0x00, 0x02), DEST_DIRECT);
         addValidationInfo(
-                Constants.MESSAGE_MENU_STATUS, new OneByteRangeValidator(0x00, 0x01), DEST_DIRECT);
+                Constants.MESSAGE_MENU_STATUS,
+                        new MinimumOneByteRangeValidator(0x00, 0x01), DEST_DIRECT);
 
         // Messages for the Remote Control Passthrough.
         addValidationInfo(
@@ -214,7 +220,7 @@
         // Messages for the Power Status.
         addValidationInfo(
                 Constants.MESSAGE_REPORT_POWER_STATUS,
-                new OneByteRangeValidator(0x00, 0x03),
+                new MinimumOneByteRangeValidator(0x00, 0x03),
                 DEST_DIRECT | DEST_BROADCAST);
 
         // Messages for the General Protocol.
@@ -229,17 +235,17 @@
                 oneByteValidator, DEST_DIRECT);
         addValidationInfo(
                 Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE,
-                new OneByteRangeValidator(0x00, 0x01),
+                new MinimumOneByteRangeValidator(0x00, 0x01),
                 DEST_ALL);
         addValidationInfo(
                 Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
-                new OneByteRangeValidator(0x00, 0x01),
+                new SingleByteRangeValidator(0x00, 0x01),
                 DEST_DIRECT);
 
         // Messages for the Audio Rate Control.
         addValidationInfo(
                 Constants.MESSAGE_SET_AUDIO_RATE,
-                new OneByteRangeValidator(0x00, 0x06),
+                new MinimumOneByteRangeValidator(0x00, 0x06),
                 DEST_DIRECT);
 
         // Messages for Feature Discovery.
@@ -900,11 +906,14 @@
         }
     }
 
-    /** Check if the given parameters are one byte parameters and within range. */
-    private static class OneByteRangeValidator implements ParameterValidator {
+    /**
+     * Check if the given parameters are at least one byte parameters
+     * and the first byte is within range.
+     */
+    private static class MinimumOneByteRangeValidator implements ParameterValidator {
         private final int mMinValue, mMaxValue;
 
-        OneByteRangeValidator(int minValue, int maxValue) {
+        MinimumOneByteRangeValidator(int minValue, int maxValue) {
             mMinValue = minValue;
             mMaxValue = maxValue;
         }
@@ -918,6 +927,26 @@
         }
     }
 
+    /** Check if the given parameters are exactly one byte parameters and within range. */
+    private static class SingleByteRangeValidator implements ParameterValidator {
+        private final int mMinValue, mMaxValue;
+
+        SingleByteRangeValidator(int minValue, int maxValue) {
+            mMinValue = minValue;
+            mMaxValue = maxValue;
+        }
+
+        @Override
+        public int isValid(byte[] params) {
+            if (params.length < 1) {
+                return ERROR_PARAMETER_SHORT;
+            } else if (params.length > 1) {
+                return ERROR_PARAMETER_LONG;
+            }
+            return toErrorCode(isWithinRange(params[0], mMinValue, mMaxValue));
+        }
+    }
+
     /**
      * Check if the given Analogue Timer message parameters are valid. Valid parameters should
      * adhere to message description of Analogue Timer defined in CEC 1.4 Specification : Message
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 75fe63a..9cd5272 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1611,6 +1611,7 @@
         @HdmiCecMessageValidator.ValidationResult
         int validationResult = message.getValidationResult();
         if (validationResult == HdmiCecMessageValidator.ERROR_PARAMETER
+                || validationResult == HdmiCecMessageValidator.ERROR_PARAMETER_LONG
                 || !verifyPhysicalAddresses(message)) {
             return Constants.ABORT_INVALID_OPERAND;
         } else if (validationResult != HdmiCecMessageValidator.OK
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c70d555..57f8d14 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -856,13 +856,15 @@
     @GuardedBy("ImfLock.class")
     private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
 
-    private static final class SoftInputShowHideHistory {
+    @VisibleForTesting
+    static final class SoftInputShowHideHistory {
         private final Entry[] mEntries = new Entry[16];
         private int mNextIndex = 0;
         private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
 
-        private static final class Entry {
+        static final class Entry {
             final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+            @Nullable
             final ClientState mClientState;
             @SoftInputModeFlags
             final int mFocusedWindowSoftInputMode;
@@ -874,7 +876,7 @@
             final boolean mInFullscreenMode;
             @NonNull
             final String mFocusedWindowName;
-            @NonNull
+            @Nullable
             final EditorInfo mEditorInfo;
             @NonNull
             final String mRequestWindowName;
@@ -953,9 +955,13 @@
 
                 pw.print(prefix);
                 pw.print(" editorInfo: ");
-                pw.print(" inputType=" + entry.mEditorInfo.inputType);
-                pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
-                pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+                if (entry.mEditorInfo != null) {
+                    pw.print(" inputType=" + entry.mEditorInfo.inputType);
+                    pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
+                    pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+                } else {
+                    pw.println("null");
+                }
 
                 pw.print(prefix);
                 pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 33e6a8f1..f0ab815 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -316,7 +316,6 @@
 import com.android.server.notification.toast.ToastRecord;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.powerstats.StatsPullAtomCallbackImpl;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -2559,8 +2558,8 @@
                         Context.STATS_MANAGER),
                 getContext().getSystemService(TelephonyManager.class),
                 LocalServices.getService(ActivityManagerInternal.class),
-                createToastRateLimiter(), new PermissionHelper(LocalServices.getService(
-                        PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(),
+                createToastRateLimiter(), new PermissionHelper(getContext(),
+                        AppGlobals.getPackageManager(),
                         AppGlobals.getPermissionManager()),
                 LocalServices.getService(UsageStatsManagerInternal.class),
                 getContext().getSystemService(TelecomManager.class),
@@ -11599,6 +11598,8 @@
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
                 listener.onNotificationPosted(sbnHolder, rankingUpdate);
+            } catch (android.os.DeadObjectException ex) {
+                Slog.wtf(TAG, "unable to notify listener (posted): " + info, ex);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
             }
@@ -11620,6 +11621,8 @@
                     reason = REASON_LISTENER_CANCEL;
                 }
                 listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
+            } catch (android.os.DeadObjectException ex) {
+                Slog.wtf(TAG, "unable to notify listener (removed): " + info, ex);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "unable to notify listener (removed): " + info, ex);
             }
@@ -11630,6 +11633,8 @@
             final INotificationListener listener = (INotificationListener) info.service;
             try {
                 listener.onNotificationRankingUpdate(rankingUpdate);
+            } catch (android.os.DeadObjectException ex) {
+                Slog.wtf(TAG, "unable to notify listener (ranking update): " + info, ex);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "unable to notify listener (ranking update): " + info, ex);
             }
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index b6fd822..93c83e1 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -25,6 +25,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.content.Context;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -37,7 +38,6 @@
 import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import java.util.Collections;
 import java.util.HashSet;
@@ -53,13 +53,13 @@
 
     private static final String NOTIFICATION_PERMISSION = Manifest.permission.POST_NOTIFICATIONS;
 
-    private final PermissionManagerServiceInternal mPmi;
+    private final Context mContext;
     private final IPackageManager mPackageManager;
     private final IPermissionManager mPermManager;
 
-    public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
+    public PermissionHelper(Context context, IPackageManager packageManager,
             IPermissionManager permManager) {
-        mPmi = pmi;
+        mContext = context;
         mPackageManager = packageManager;
         mPermManager = permManager;
     }
@@ -71,7 +71,7 @@
     public boolean hasPermission(int uid) {
         final long callingId = Binder.clearCallingIdentity();
         try {
-            return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+            return mContext.checkPermission(NOTIFICATION_PERMISSION, -1, uid) == PERMISSION_GRANTED;
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
@@ -193,8 +193,8 @@
                 return;
             }
 
-            boolean currentlyGranted = mPmi.checkPermission(packageName, NOTIFICATION_PERMISSION,
-                    userId) != PackageManager.PERMISSION_DENIED;
+            int uid = mPackageManager.getPackageUid(packageName, 0, userId);
+            boolean currentlyGranted = hasPermission(uid);
             if (grant && !currentlyGranted) {
                 mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
             } else if (!grant && currentlyGranted) {
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index f971db9..e796275 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -161,7 +161,13 @@
                     mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
                     if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
                         mWaitingForTrustableDowngrade = true;
-                        setSecurityWindowTimer();
+                        resultCallback.thenAccept(result -> {
+                            if (result.getStatus() == GrantTrustResult.STATUS_UNLOCKED_BY_GRANT) {
+                                // if we are not unlocked by grantTrust, then we don't need to
+                                // have the timer for the security window
+                                setSecurityWindowTimer();
+                            }
+                        });
                     } else {
                         mWaitingForTrustableDowngrade = false;
                     }
@@ -562,6 +568,7 @@
      * @see android.service.trust.TrustAgentService#onDeviceLocked()
      */
     public void onDeviceLocked() {
+        mWithinSecurityLockdownWindow = false;
         try {
             if (mTrustAgentService != null) mTrustAgentService.onDeviceLocked();
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 8786005..1ab9823 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -393,6 +393,23 @@
                 true /* overrideHardTimeout */);
     }
 
+    private void cancelBothTrustableAlarms(int userId) {
+        TrustableTimeoutAlarmListener idleTimeout =
+                mIdleTrustableTimeoutAlarmListenerForUser.get(
+                        userId);
+        TrustableTimeoutAlarmListener trustableTimeout =
+                mTrustableTimeoutAlarmListenerForUser.get(
+                        userId);
+        if (idleTimeout != null && idleTimeout.isQueued()) {
+            idleTimeout.setQueued(false);
+            mAlarmManager.cancel(idleTimeout);
+        }
+        if (trustableTimeout != null && trustableTimeout.isQueued()) {
+            trustableTimeout.setQueued(false);
+            mAlarmManager.cancel(trustableTimeout);
+        }
+    }
+
     private void handleScheduleTrustedTimeout(int userId, boolean shouldOverride) {
         long when = SystemClock.elapsedRealtime() + TRUST_TIMEOUT_IN_MILLIS;
         TrustedTimeoutAlarmListener alarm = mTrustTimeoutAlarmListenerForUser.get(userId);
@@ -657,6 +674,11 @@
                 resultCallback.complete(new GrantTrustResult(STATUS_UNLOCKED_BY_GRANT));
             }
         }
+
+        if ((wasTrusted || wasTrustable) && pendingTrustState == TrustState.UNTRUSTED) {
+            if (DEBUG) Slog.d(TAG, "Trust was revoked, destroy trustable alarms");
+            cancelBothTrustableAlarms(userId);
+        }
     }
 
     private void updateTrustUsuallyManaged(int userId, boolean managed) {
@@ -1908,7 +1930,11 @@
                     handleScheduleTrustTimeout(shouldOverride, timeoutType);
                     break;
                 case MSG_REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH:
-                    refreshTrustableTimers(msg.arg1);
+                    TrustableTimeoutAlarmListener trustableAlarm =
+                            mTrustableTimeoutAlarmListenerForUser.get(msg.arg1);
+                    if (trustableAlarm != null && trustableAlarm.isQueued()) {
+                        refreshTrustableTimers(msg.arg1);
+                    }
                     break;
             }
         }
@@ -2160,7 +2186,7 @@
             TrustedTimeoutAlarmListener otherAlarm;
             boolean otherAlarmPresent;
             if (ENABLE_ACTIVE_UNLOCK_FLAG) {
-                cancelBothTrustableAlarms();
+                cancelBothTrustableAlarms(mUserId);
                 otherAlarm = mTrustTimeoutAlarmListenerForUser.get(mUserId);
                 otherAlarmPresent = (otherAlarm != null) && otherAlarm.isQueued();
                 if (otherAlarmPresent) {
@@ -2172,23 +2198,6 @@
             }
         }
 
-        private void cancelBothTrustableAlarms() {
-            TrustableTimeoutAlarmListener idleTimeout =
-                    mIdleTrustableTimeoutAlarmListenerForUser.get(
-                            mUserId);
-            TrustableTimeoutAlarmListener trustableTimeout =
-                    mTrustableTimeoutAlarmListenerForUser.get(
-                            mUserId);
-            if (idleTimeout != null && idleTimeout.isQueued()) {
-                idleTimeout.setQueued(false);
-                mAlarmManager.cancel(idleTimeout);
-            }
-            if (trustableTimeout != null && trustableTimeout.isQueued()) {
-                trustableTimeout.setQueued(false);
-                mAlarmManager.cancel(trustableTimeout);
-            }
-        }
-
         private void disableRenewableTrustWhileNonrenewableTrustIsPresent() {
             // if non-renewable trust is running, we need to temporarily prevent
             // renewable trust from being used
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 98d2d3d..9020cb3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1353,9 +1353,6 @@
 
         void complete() {
             // Only changes from home+lock to just home or lock need attention
-            // If setting the wallpaper fails, this callback will be called
-            // when the wallpaper is detached, in which case wallpapers may have
-            // already changed. Make sure we're not overwriting a more recent wallpaper.
             if (mNewWallpaper.mSystemWasBoth) {
                 if (DEBUG) {
                     Slog.v(TAG, "Handling change from system+lock wallpaper");
@@ -1378,7 +1375,8 @@
                                     mOriginalSystem.wallpaperComponent;
                             lockWp.connection = mOriginalSystem.connection;
                             lockWp.connection.mWallpaper = lockWp;
-                            updateEngineFlags(mOriginalSystem, FLAG_LOCK);
+                            mOriginalSystem.mWhich = FLAG_LOCK;
+                            updateEngineFlags(mOriginalSystem);
                             notifyWallpaperColorsChanged(lockWp, FLAG_LOCK);
                         } else {
                             // Failed rename, use current system wp for both
@@ -1387,7 +1385,7 @@
                             }
                             WallpaperData currentSystem = mWallpaperMap.get(mNewWallpaper.userId);
                             currentSystem.mWhich = FLAG_SYSTEM | FLAG_LOCK;
-                            updateEngineFlags(currentSystem, FLAG_SYSTEM | FLAG_LOCK);
+                            updateEngineFlags(currentSystem);
                             mLockWallpaperMap.remove(mNewWallpaper.userId);
                         }
                     } else {
@@ -1396,7 +1394,7 @@
                             Slog.v(TAG, "live system+lock to system success");
                         }
                         mOriginalSystem.mWhich = FLAG_LOCK;
-                        updateEngineFlags(mOriginalSystem, FLAG_LOCK);
+                        updateEngineFlags(mOriginalSystem);
                         mLockWallpaperMap.put(mNewWallpaper.userId, mOriginalSystem);
                         mLastLockWallpaper = mOriginalSystem;
                         notifyWallpaperColorsChanged(mOriginalSystem, FLAG_LOCK);
@@ -1409,7 +1407,7 @@
                     WallpaperData currentSystem = mWallpaperMap.get(mNewWallpaper.userId);
                     if (currentSystem.wallpaperId == mOriginalSystem.wallpaperId) {
                         currentSystem.mWhich = FLAG_SYSTEM;
-                        updateEngineFlags(currentSystem, FLAG_SYSTEM);
+                        updateEngineFlags(currentSystem);
                     }
                 }
             }
@@ -1422,24 +1420,6 @@
                 Slog.v(TAG, "new lastLockWp: " + mLastLockWallpaper);
             }
         }
-
-        private void updateEngineFlags(WallpaperData wallpaper, @SetWallpaperFlags int which) {
-            if (wallpaper.connection == null) {
-                return;
-            }
-            wallpaper.connection.forEachDisplayConnector(
-                    connector -> {
-                        try {
-                            if (connector.mEngine != null) {
-                                connector.mEngine.setWallpaperFlags(which);
-                                mWindowManagerInternal.setWallpaperShowWhenLocked(
-                                        connector.mToken, (which & FLAG_LOCK) != 0);
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Failed to update wallpaper engine flags", e);
-                        }
-                    });
-        }
     }
 
     class MyPackageMonitor extends PackageMonitor {
@@ -3095,6 +3075,9 @@
                                 newWallpaper.userId);
                         if (lockedWallpaper != null) {
                             detachWallpaperLocked(lockedWallpaper);
+                            if (same) {
+                                updateEngineFlags(newWallpaper);
+                            }
                         }
                         mLockWallpaperMap.remove(newWallpaper.userId);
                     }
@@ -3430,6 +3413,27 @@
         }
     }
 
+    // Updates the given wallpaper's Engine so that its destination flags are the same as those of
+    // the wallpaper, e.g., after a wallpaper has been changed from displaying on home+lock to home
+    // or lock only.
+    private void updateEngineFlags(WallpaperData wallpaper) {
+        if (wallpaper.connection == null) {
+            return;
+        }
+        wallpaper.connection.forEachDisplayConnector(
+                connector -> {
+                    try {
+                        if (connector.mEngine != null) {
+                            connector.mEngine.setWallpaperFlags(wallpaper.mWhich);
+                            mWindowManagerInternal.setWallpaperShowWhenLocked(
+                                    connector.mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Failed to update wallpaper engine flags", e);
+                    }
+                });
+    }
+
     private void clearWallpaperComponentLocked(WallpaperData wallpaper) {
         wallpaper.wallpaperComponent = null;
         detachWallpaperLocked(wallpaper);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 78c066b..c3cd3ec 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5400,7 +5400,9 @@
             return;
         }
         if (inFinishingTransition) {
-            // Let the finishing transition commit the visibility.
+            // Let the finishing transition commit the visibility, but let the controller know
+            // about it so that we can recover from degenerate cases.
+            mTransitionController.mValidateCommitVis.add(this);
             return;
         }
         // If we are preparing an app transition, then delay changing
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index f5079d3..e47787e 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -33,6 +33,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.Process.SYSTEM_UID;
+import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 
@@ -209,7 +210,8 @@
     private final PointerEventListener mListener = new PointerEventListener() {
         @Override
         public void onPointerEvent(MotionEvent ev) {
-            if (!mFreezeTaskListReordering || ev.getAction() != MotionEvent.ACTION_DOWN) {
+            if (!mFreezeTaskListReordering || ev.getAction() != MotionEvent.ACTION_DOWN
+                    || ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE) {
                 // Skip if we aren't freezing or starting a gesture
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3f4296a..d3edeae 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3239,7 +3239,7 @@
             if (task.getActivity(activity -> !activity.finishing && activity.mUserId == userId)
                     != null) {
                 mService.getTaskChangeNotificationController().notifyTaskProfileLocked(
-                        task.getTaskInfo());
+                        task.getTaskInfo(), userId);
             }
         }, true /* traverseTopToBottom */);
     }
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index b5df3e0..bbb8563 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -193,6 +193,7 @@
                                 .setSourceCrop(new Rect(0, 0, width, height))
                                 .setAllowProtected(true)
                                 .setCaptureSecureLayers(true)
+                                .setHintForSeamlessTransition(true)
                                 .build();
                 screenshotBuffer = ScreenCapture.captureDisplay(captureArgs);
             } else {
@@ -202,6 +203,7 @@
                                 .setCaptureSecureLayers(true)
                                 .setAllowProtected(true)
                                 .setSourceCrop(new Rect(0, 0, width, height))
+                                .setHintForSeamlessTransition(true)
                                 .build();
                 screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
             }
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 49d064f..9324e29 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -144,7 +144,7 @@
     };
 
     private final TaskStackConsumer mNotifyTaskProfileLocked = (l, m) -> {
-        l.onTaskProfileLocked((RunningTaskInfo) m.obj);
+        l.onTaskProfileLocked((RunningTaskInfo) m.obj, m.arg1);
     };
 
     private final TaskStackConsumer mNotifyTaskSnapshotChanged = (l, m) -> {
@@ -467,9 +467,9 @@
      * Notify listeners that the task has been put in a locked state because one or more of the
      * activities inside it belong to a managed profile user that has been locked.
      */
-    void notifyTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo) {
+    void notifyTaskProfileLocked(RunningTaskInfo taskInfo, int userId) {
         final Message msg = mHandler.obtainMessage(NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG,
-                taskInfo);
+                userId, 0, taskInfo);
         forAllLocalListeners(mNotifyTaskProfileLocked, msg);
         msg.sendToTarget();
     }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d531ad1..7bd3b32 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -55,6 +55,7 @@
 import static android.window.TransitionInfo.FLAG_TASK_LAUNCHING_BEHIND;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT;
 
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
@@ -65,12 +66,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.IApplicationThread;
 import android.content.pm.ActivityInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
@@ -85,6 +88,7 @@
 import android.view.WindowManager;
 import android.window.ScreenCapture;
 import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
@@ -185,7 +189,7 @@
     final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
 
     /** The final animation targets derived from participants after promotion. */
-    private ArrayList<ChangeInfo> mTargets;
+    ArrayList<ChangeInfo> mTargets;
 
     /** The displays that this transition is running on. */
     private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>();
@@ -271,9 +275,14 @@
     /** Any 2 transitions of this type can run in parallel with each other. Used for testing. */
     static final int PARALLEL_TYPE_MUTUAL = 1;
 
+    /** This is a recents transition. */
+    static final int PARALLEL_TYPE_RECENTS = 2;
+
+
     @IntDef(prefix = { "PARALLEL_TYPE_" }, value = {
             PARALLEL_TYPE_NONE,
-            PARALLEL_TYPE_MUTUAL
+            PARALLEL_TYPE_MUTUAL,
+            PARALLEL_TYPE_RECENTS
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface ParallelType {}
@@ -328,6 +337,21 @@
         mFlags |= flag;
     }
 
+    void calcParallelCollectType(WindowContainerTransaction wct) {
+        for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
+            final WindowContainerTransaction.HierarchyOp hop = wct.getHierarchyOps().get(i);
+            if (hop.getType() != HIERARCHY_OP_TYPE_PENDING_INTENT) continue;
+            final Bundle b = hop.getLaunchOptions();
+            if (b == null || b.isEmpty()) continue;
+            final boolean transientLaunch = b.getBoolean(ActivityOptions.KEY_TRANSIENT_LAUNCH);
+            if (transientLaunch) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "Starting a Recents transition which can be parallel.");
+                mParallelCollectType = PARALLEL_TYPE_RECENTS;
+            }
+        }
+    }
+
     /** Records an activity as transient-launch. This activity must be already collected. */
     void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) {
         if (mTransientLaunches == null) {
@@ -380,6 +404,10 @@
         return false;
     }
 
+    boolean hasTransientLaunch() {
+        return mTransientLaunches != null && !mTransientLaunches.isEmpty();
+    }
+
     boolean isTransientLaunch(@NonNull ActivityRecord activity) {
         return mTransientLaunches != null && mTransientLaunches.containsKey(activity);
     }
@@ -984,13 +1012,20 @@
             // Record all the now-hiding activities so that they are committed. Just use
             // mParticipants because we can avoid a new list this way.
             for (int i = 0; i < mTransientHideTasks.size(); ++i) {
-                // Only worry about tasks that were actually hidden. Otherwise, we could end-up
-                // committing visibility for activity-level changes that aren't part of this
-                // transition.
-                if (mTransientHideTasks.get(i).isVisibleRequested()) continue;
-                mTransientHideTasks.get(i).forAllActivities(r -> {
+                final Task rootTask = mTransientHideTasks.get(i);
+                rootTask.forAllActivities(r -> {
                     // Only check leaf-tasks that were collected
                     if (!mParticipants.contains(r.getTask())) return;
+                    if (rootTask.isVisibleRequested()) {
+                        // This transient-hide didn't hide, so don't commit anything (otherwise we
+                        // could prematurely commit invisible on unrelated activities). To be safe,
+                        // though, notify the controller to prevent degenerate cases.
+                        if (!r.isVisibleRequested()) {
+                            mController.mValidateCommitVis.add(r);
+                        }
+                        return;
+                    }
+                    // This did hide: commit immediately so that other transitions know about it.
                     mParticipants.add(r);
                 });
             }
@@ -2964,11 +2999,14 @@
 
             Rect cropBounds = new Rect(bounds);
             cropBounds.offsetTo(0, 0);
+            final boolean isDisplayRotation = wc.asDisplayContent() != null
+                    && wc.asDisplayContent().isRotationChanging();
             ScreenCapture.LayerCaptureArgs captureArgs =
                     new ScreenCapture.LayerCaptureArgs.Builder(wc.getSurfaceControl())
                             .setSourceCrop(cropBounds)
                             .setCaptureSecureLayers(true)
                             .setAllowProtected(true)
+                            .setHintForSeamlessTransition(isDisplayRotation)
                             .build();
             ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
                     ScreenCapture.captureLayers(captureArgs);
@@ -2979,8 +3017,6 @@
                 Slog.w(TAG, "Failed to capture screenshot for " + wc);
                 return false;
             }
-            final boolean isDisplayRotation = wc.asDisplayContent() != null
-                    && wc.asDisplayContent().isRotationChanging();
             // Some tests may check the name "RotationLayer" to detect display rotation.
             final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc;
             SurfaceControl snapshotSurface = wc.makeAnimationLeash()
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 7950eda..e1da91a 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -133,6 +133,13 @@
     final ArrayList<Runnable> mStateValidators = new ArrayList<>();
 
     /**
+     * List of activity-records whose visibility changed outside the main/tracked part of a
+     * transition (eg. in the finish-transaction). These will be checked when idle to recover from
+     * degenerate states.
+     */
+    final ArrayList<ActivityRecord> mValidateCommitVis = new ArrayList<>();
+
+    /**
      * Currently playing transitions (in the order they were started). When finished, records are
      * removed from this list.
      */
@@ -848,6 +855,15 @@
             }
         }
         mStateValidators.clear();
+        for (int i = 0; i < mValidateCommitVis.size(); ++i) {
+            final ActivityRecord ar = mValidateCommitVis.get(i);
+            if (!ar.isVisibleRequested() && ar.isVisible()) {
+                Slog.e(TAG, "Uncommitted visibility change: " + ar);
+                ar.commitVisibility(ar.isVisibleRequested(), false /* layout */,
+                        false /* fromTransition */);
+            }
+        }
+        mValidateCommitVis.clear();
     }
 
     /**
@@ -879,6 +895,8 @@
             // If it's a legacy sync, then it needs to wait until there is no collecting transition.
             if (queued.mTransition == null) return;
             if (!canStartCollectingNow(queued.mTransition)) return;
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from collecting"
+                    + " to waiting.", mCollectingTransition.getSyncId());
             mWaitingTransitions.add(mCollectingTransition);
             mCollectingTransition = null;
         } else if (mSyncEngine.hasActiveSync()) {
@@ -930,10 +948,37 @@
      * in {@link #getIsIndependent} later.
      */
     boolean getCanBeIndependent(Transition collecting, Transition queued) {
+        // For tests
         if (queued.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
                 && collecting.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) {
             return true;
         }
+        // For recents
+        if (queued.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
+            if (collecting.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
+                // Must serialize with itself.
+                return false;
+            }
+            // allow this if `collecting` only has activities
+            for (int i = 0; i < collecting.mParticipants.size(); ++i) {
+                final WindowContainer wc = collecting.mParticipants.valueAt(i);
+                final ActivityRecord ar = wc.asActivityRecord();
+                if (ar == null && wc.asWindowState() == null && wc.asWindowToken() == null) {
+                    // Is task or above, so can't be independent
+                    return false;
+                }
+                if (ar != null && ar.isActivityTypeHomeOrRecents()) {
+                    // It's a recents or home type, so it conflicts.
+                    return false;
+                }
+            }
+            return true;
+        } else if (collecting.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
+            // We can collect simultaneously with recents if it is populated. This is because
+            // we know that recents will not collect/trampoline any more stuff. If anything in the
+            // queued transition overlaps, it will end up just waiting in sync-queue anyways.
+            return true;
+        }
         return false;
     }
 
@@ -942,11 +987,47 @@
      * `running` is playing based on its current state.
      */
     static boolean getIsIndependent(Transition running, Transition incoming) {
+        // For tests
         if (running.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
                 && incoming.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) {
             return true;
         }
-        return false;
+        // For now there's only one mutually-independent pair: an all activity-level transition and
+        // a transient-launch where none of the activities are part of the transient-launch task,
+        // so the following logic is hard-coded specifically for this.
+        // Also, we currently restrict valid transient-launches to just recents.
+        final Transition recents;
+        final Transition other;
+        if (running.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS
+                && running.hasTransientLaunch()) {
+            if (incoming.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
+                // Recents can't be independent from itself.
+                return false;
+            }
+            recents = running;
+            other = incoming;
+        } else if (incoming.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS
+                && incoming.hasTransientLaunch()) {
+            recents = incoming;
+            other = running;
+        } else {
+            return false;
+        }
+        // Check against *targets* because that is the post-promotion set of containers that are
+        // actually animating.
+        for (int i = 0; i < other.mTargets.size(); ++i) {
+            final WindowContainer wc = other.mTargets.get(i).mContainer;
+            final ActivityRecord ar = wc.asActivityRecord();
+            if (ar == null && wc.asWindowState() == null && wc.asWindowToken() == null) {
+                // Is task or above, so for now don't let them be independent.
+                return false;
+            }
+            if (ar != null && recents.isTransientLaunch(ar)) {
+                // Change overlaps with recents, so serialize.
+                return false;
+            }
+        }
+        return true;
     }
 
     void assignTrack(Transition transition, TransitionInfo info) {
@@ -970,9 +1051,15 @@
         if (track < 0) {
             // Didn't overlap with anything, so give it its own track
             track = mTrackCount;
+            if (track > 0) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Playing #%d in parallel on "
+                        + "track #%d", transition.getSyncId(), track);
+            }
         }
         if (sync) {
             info.setFlags(info.getFlags() | TransitionInfo.FLAG_SYNC);
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Marking #%d animation as SYNC.",
+                    transition.getSyncId());
         }
         transition.mAnimationTrack = track;
         info.setTrack(track);
@@ -1145,6 +1232,8 @@
                 // Check if we can run in parallel here.
                 if (canStartCollectingNow(transit)) {
                     // start running in parallel.
+                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from"
+                            + " collecting to waiting.", mCollectingTransition.getSyncId());
                     mWaitingTransitions.add(mCollectingTransition);
                     mCollectingTransition = null;
                     moveToCollecting(transit);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index cd42528..d5aa520 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -299,6 +299,7 @@
                     final boolean needsSetReady = t != null;
                     final Transition nextTransition = new Transition(type, 0 /* flags */,
                             mTransitionController, mService.mWindowManager.mSyncEngine);
+                    nextTransition.calcParallelCollectType(wct);
                     mTransitionController.startCollectOrQueue(nextTransition,
                             (deferred) -> {
                                 nextTransition.start();
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index fc7fd1a..b073ff4 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -98,7 +98,9 @@
                             mRequestId, mClientRequest,
                             mClientAppInfo.getPackageName(),
                             PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
-                                    Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+                                    Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+                            // TODO(b/279480457): populate
+                            /*defaultProviderId=*/new ArrayList<>()),
                     providerDataList);
             mClientCallback.onPendingIntent(mPendingIntent);
         } catch (RemoteException e) {
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 50e5163..4e82ee7 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -35,6 +35,7 @@
 /**
  * For all future metric additions, this will contain their names for local usage after importing
  * from {@link com.android.internal.util.FrameworkStatsLog}.
+ * TODO(b/271135048) - Emit all atoms, including all V4 atoms (specifically the rest of track 1).
  */
 public class MetricUtilities {
     private static final boolean LOG_FLAG = true;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 520b937..40514b2 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -75,7 +75,8 @@
         CreateCredentialRequest providerCreateRequest =
                 createProviderRequest(providerInfo.getCapabilities(),
                         createRequestSession.mClientRequest,
-                        createRequestSession.mClientAppInfo);
+                        createRequestSession.mClientAppInfo,
+                        providerInfo.isSystemProvider());
         if (providerCreateRequest != null) {
             return new ProviderCreateSession(
                     context,
@@ -114,9 +115,16 @@
     }
 
     @Nullable
-    private static CreateCredentialRequest createProviderRequest(List<String> providerCapabilities,
+    private static CreateCredentialRequest createProviderRequest(
+            List<String> providerCapabilities,
             android.credentials.CreateCredentialRequest clientRequest,
-            CallingAppInfo callingAppInfo) {
+            CallingAppInfo callingAppInfo,
+            boolean isSystemProvider) {
+        if (clientRequest.isSystemProviderRequired() && !isSystemProvider) {
+            // Request requires system provider but this session does not correspond to a
+            // system service
+            return null;
+        }
         String capability = clientRequest.getType();
         if (providerCapabilities.contains(capability)) {
             return new CreateCredentialRequest(callingAppInfo, capability,
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index a62d9e8..0c3d2a4 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -40,7 +40,6 @@
 import android.service.credentials.CredentialProviderService;
 import android.service.credentials.GetCredentialRequest;
 import android.service.credentials.RemoteEntry;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 
@@ -413,11 +412,9 @@
      */
     private boolean onAuthenticationEntrySelected(
             @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
-        Log.i(TAG, "onAuthenticationEntrySelected");
         // Authentication entry is expected to have a BeginGetCredentialResponse instance. If it
         // does not have it, we remove the authentication entry and do not add any more content.
         if (providerPendingIntentResponse == null) {
-            Log.i(TAG, "providerPendingIntentResponse is null");
             // Nothing received. This is equivalent to no content received.
             return false;
         }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index c10f564..ead86ce 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -266,7 +266,7 @@
                 .collect(Collectors.toList());
         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
                 /*source=*/ CredentialsSource.REGISTRY);
-        // TODO(use metric later)
+        // TODO(b/273353677) : metric should be emitted similarly to sibling classes
     }
 
     @Nullable
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index d02a8c1..73fdc1c 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -268,12 +268,9 @@
                     /*pId=*/-1, appInfo.uid) == PackageManager.PERMISSION_GRANTED) {
                 return true;
             }
-        } catch (SecurityException e) {
+        } catch (SecurityException | PackageManager.NameNotFoundException e) {
             Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
             return false;
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.i(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
-            return false;
         }
         return false;
     }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
index 721d3d7..b212606 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
@@ -30,7 +30,6 @@
  * Some types are redundant across these metric collectors, but that has debug use-cases as
  * these data-types are available at different moments of the flow (and typically, one can feed
  * into the next).
- * TODO(b/270403549) - iterate on this in V3+
  */
 public class CandidatePhaseMetric {
 
@@ -56,10 +55,7 @@
     private int mProviderQueryStatus = -1;
     // Indicates if an exception was thrown by this provider, false by default
     private boolean mHasException = false;
-    // Indicates the number of total entries available. We can also locally store the entries, but
-    // cannot emit them in the current split form. TODO(b/271135048) - possibly readjust candidate
-    // entries. Also, it may be okay to remove this and instead aggregate from inner counts.
-    // Defaults to -1
+    // Indicates the number of total entries available, defaults to -1
     private int mNumEntriesTotal = -1;
     // The count of action entries from this provider, defaults to -1
     private int mActionEntryCount = -1;
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index c80cc24..8f08bb0 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -29,11 +29,8 @@
  * Some types are redundant across these metric collectors, but that has debug use-cases as
  * these data-types are available at different moments of the flow (and typically, one can feed
  * into the next).
- * TODO(b/270403549) - iterate on this in V3+
  */
 public class ChosenProviderFinalPhaseMetric {
-
-    // TODO(b/270403549) - applies elsewhere, likely removed or replaced w/ some hashed/count index
     private static final String TAG = "ChosenFinalPhaseMetric";
     // The session id associated with this API call, used to unite split emits
     private int mSessionId = -1;
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 0ecd9cc..5cfb0e7 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -26,7 +26,6 @@
  * Some types are redundant across these metric collectors, but that has debug use-cases as
  * these data-types are available at different moments of the flow (and typically, one can feed
  * into the next).
- * TODO(b/270403549) - iterate on this in V3+
  */
 public class InitialPhaseMetric {
     private static final String TAG = "InitialPhaseMetric";
@@ -47,7 +46,6 @@
     private long mCredentialServiceBeginQueryTimeNanoseconds = -1;
 
     // Indicates if the origin was specified when making this API request
-    // TODO(b/271135048) - Emit once metrics approved
     private boolean mOriginSpecified = false;
 
     // Stores the deduped request information, particularly {"req":5}.
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
index 547c09a..4ecdfef 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -337,10 +337,6 @@
      */
     public void logApiCalledAtFinish(int apiStatus) {
         try {
-            // TODO (b/270403549) - this browsing phase object is fine but also have a new emit
-            // For the returned types by authentication entries - i.e. a CandidatePhase During
-            // Browse
-            // Possibly think of adding in more atoms for other APIs as well.
             logApiCalledFinalPhase(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric,
                     apiStatus,
                     ++mSequenceCounter);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3908777..675ebd3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -39,6 +39,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DISPLAY;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_FUN;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_INPUT_METHODS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_KEYGUARD;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCALE;
@@ -12195,7 +12196,7 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
@@ -12205,7 +12206,7 @@
         int userId = getProfileParentUserIfRequested(
                 caller.getUserId(), calledOnParentInstance);
         if (calledOnParentInstance) {
-            if (!isPermissionCheckFlagEnabled()) {
+            if (!isPolicyEngineForFinanceFlagEnabled()) {
                 Preconditions.checkCallAuthorization(
                         isProfileOwnerOfOrganizationOwnedDevice(caller));
             }
@@ -12213,7 +12214,7 @@
                     "Permitted input methods must allow all input methods or only "
                             + "system input methods when called on the parent instance of an "
                             + "organization-owned device");
-        } else if (!isPermissionCheckFlagEnabled()) {
+        } else if (!isPolicyEngineForFinanceFlagEnabled()) {
             Preconditions.checkCallAuthorization(
                     isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         }
@@ -12241,7 +12242,9 @@
 
         synchronized (getLockObject()) {
             if (isPolicyEngineForFinanceFlagEnabled()) {
-                EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackageName);
+                EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
+                        who, MANAGE_DEVICE_POLICY_INPUT_METHODS,
+                        caller.getPackageName(), userId);
                 mDevicePolicyEngine.setLocalPolicy(
                         PolicyDefinition.PERMITTED_INPUT_METHODS,
                         admin,
@@ -13436,6 +13439,13 @@
     public void setUserRestrictionGlobally(String callerPackage, String key) {
         final CallerIdentity caller = getCallerIdentity(callerPackage);
 
+        EnforcingAdmin admin = enforcePermissionForUserRestriction(
+                /* who= */ null,
+                key,
+                caller.getPackageName(),
+                UserHandle.USER_ALL
+        );
+
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
 
         if (!isPolicyEngineForFinanceFlagEnabled()) {
@@ -13452,13 +13462,6 @@
             throw new IllegalArgumentException("Invalid restriction key: " + key);
         }
 
-        EnforcingAdmin admin = enforcePermissionForUserRestriction(
-                /* who= */ null,
-                key,
-                caller.getPackageName(),
-                UserHandle.USER_ALL
-        );
-
         setGlobalUserRestrictionInternal(admin, key, /* enabled= */ true);
 
         logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller);
@@ -16371,7 +16374,8 @@
                     // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return
                     // the admin for the calling user.
                     Slogf.w(LOG_TAG, "getEnforcingAdminAndUserDetailsInternal(%d, %s): multiple "
-                            + "sources for restriction %s on user %d", restriction, userId);
+                            + "sources for restriction %s on user %d",
+                            userId, restriction, restriction, userId);
                     result = new Bundle();
                     result.putInt(Intent.EXTRA_USER_ID, userId);
                     return result;
@@ -22839,6 +22843,7 @@
             MANAGE_DEVICE_POLICY_DISPLAY,
             MANAGE_DEVICE_POLICY_FACTORY_RESET,
             MANAGE_DEVICE_POLICY_FUN,
+            MANAGE_DEVICE_POLICY_INPUT_METHODS,
             MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
             MANAGE_DEVICE_POLICY_KEYGUARD,
             MANAGE_DEVICE_POLICY_LOCALE,
@@ -22914,9 +22919,11 @@
                     MANAGE_DEVICE_POLICY_BLUETOOTH,
                     MANAGE_DEVICE_POLICY_CALLS,
                     MANAGE_DEVICE_POLICY_CAMERA,
+                    MANAGE_DEVICE_POLICY_CERTIFICATES,
                     MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
                     MANAGE_DEVICE_POLICY_DISPLAY,
                     MANAGE_DEVICE_POLICY_FACTORY_RESET,
+                    MANAGE_DEVICE_POLICY_INPUT_METHODS,
                     MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
                     MANAGE_DEVICE_POLICY_KEYGUARD,
                     MANAGE_DEVICE_POLICY_LOCALE,
@@ -22949,7 +22956,6 @@
                     MANAGE_DEVICE_POLICY_ACROSS_USERS,
                     MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
                     MANAGE_DEVICE_POLICY_APPS_CONTROL,
-                    MANAGE_DEVICE_POLICY_CERTIFICATES,
                     MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
                     MANAGE_DEVICE_POLICY_DEFAULT_SMS,
                     MANAGE_DEVICE_POLICY_LOCALE,
@@ -23074,11 +23080,12 @@
     //Map of Permission to Delegate Scope.
     private static final HashMap<String, String> DELEGATE_SCOPES = new HashMap<>();
     {
-        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, DELEGATION_APP_RESTRICTIONS);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, DELEGATION_BLOCK_UNINSTALL);
-        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING);
+        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_CERTIFICATES, DELEGATION_CERT_INSTALL);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, DELEGATION_PACKAGE_ACCESS);
+        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT);
+        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING);
     }
 
     private static final HashMap<String, String> CROSS_USER_PERMISSIONS =
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 3a47b47..2b57c59 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -154,6 +154,7 @@
         // Freeze time for testing.
         long nowElapsed;
         boolean useMotionSensor = true;
+        boolean isLocationPrefetchEnabled = true;
 
         InjectorForTest(Context ctx) {
             super(ctx);
@@ -223,6 +224,11 @@
         }
 
         @Override
+        boolean isLocationPrefetchEnabled() {
+            return isLocationPrefetchEnabled;
+        }
+
+        @Override
         PowerManager getPowerManager() {
             return mPowerManager;
         }
@@ -991,6 +997,43 @@
     }
 
     @Test
+    public void testStepIdleStateLocked_ValidStates_LocationPrefetchDisabled() {
+        mInjector.locationManager = mLocationManager;
+        mInjector.isLocationPrefetchEnabled = false;
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+        doReturn(mock(LocationProvider.class)).when(mLocationManager).getProvider(anyString());
+        // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon.
+        setAlarmSoon(false);
+        // Set state to INACTIVE.
+        mDeviceIdleController.becomeActiveLocked("testing", 0);
+        setChargingOn(false);
+        setScreenOn(false);
+        verifyStateConditions(STATE_INACTIVE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_PENDING);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_SENSING);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        // Prefetch location is off, so SENSING should go straight through to IDLE.
+        verifyStateConditions(STATE_IDLE);
+
+        // Should just alternate between IDLE and IDLE_MAINTENANCE now.
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_MAINTENANCE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_MAINTENANCE);
+    }
+
+    @Test
     public void testStepIdleStateLocked_ValidStates_WithLocationManager_NoProviders() {
         // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon.
         setAlarmSoon(false);
@@ -1024,6 +1067,46 @@
     }
 
     @Test
+    public void testStepIdleStateLocked_ValidStates_WithLocationManager_MissingProviders() {
+        mInjector.locationManager = mLocationManager;
+        doReturn(null).when(mLocationManager)
+                .getProvider(eq(LocationManager.FUSED_PROVIDER));
+        doReturn(null).when(mLocationManager)
+                .getProvider(eq(LocationManager.GPS_PROVIDER));
+        doReturn(mock(LocationProvider.class)).when(mLocationManager)
+                .getProvider(eq(LocationManager.NETWORK_PROVIDER));
+        // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon.
+        setAlarmSoon(false);
+        // Set state to INACTIVE.
+        mDeviceIdleController.becomeActiveLocked("testing", 0);
+        setChargingOn(false);
+        setScreenOn(false);
+        verifyStateConditions(STATE_INACTIVE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_PENDING);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_SENSING);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        // Location manager exists, but the required providers don't exist,
+        // so SENSING should go straight through to IDLE.
+        verifyStateConditions(STATE_IDLE);
+
+        // Should just alternate between IDLE and IDLE_MAINTENANCE now.
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_MAINTENANCE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_MAINTENANCE);
+    }
+
+    @Test
     public void testStepIdleStateLocked_ValidStates_WithLocationManager_WithProviders() {
         mInjector.locationManager = mLocationManager;
         doReturn(mock(LocationProvider.class)).when(mLocationManager).getProvider(anyString());
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
deleted file mode 100644
index 17fba9f..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.when;
-
-import android.hardware.display.DisplayManager;
-import android.provider.Settings;
-import android.testing.TestableContext;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.display.RefreshRateSettingsUtils;
-
-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;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RefreshRateSettingsUtilsTest {
-
-    @Rule
-    public final TestableContext mContext = new TestableContext(
-            InstrumentationRegistry.getInstrumentation().getContext());
-
-    @Mock
-    private DisplayManager mDisplayManagerMock;
-    @Mock
-    private Display mDisplayMock;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mContext.addMockSystemService(DisplayManager.class, mDisplayManagerMock);
-
-        Display.Mode[] modes = new Display.Mode[] {
-                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
-                        /* refreshRate= */ 60),
-                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
-                        /* refreshRate= */ 120),
-                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
-                        /* refreshRate= */ 90)
-        };
-
-        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
-        when(mDisplayMock.getSupportedModes()).thenReturn(modes);
-    }
-
-    @Test
-    public void testFindHighestRefreshRateForDefaultDisplay() {
-        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null);
-        assertEquals(DEFAULT_REFRESH_RATE,
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
-                /* delta= */ 0);
-
-        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
-        assertEquals(120,
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
-                /* delta= */ 0);
-    }
-
-    @Test
-    public void testGetMinRefreshRate() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.FORCE_PEAK_REFRESH_RATE, -1);
-        assertEquals(0, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0);
-
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.FORCE_PEAK_REFRESH_RATE, 0);
-        assertEquals(0, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0);
-
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.FORCE_PEAK_REFRESH_RATE, 1);
-        assertEquals(120, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0);
-    }
-
-    @Test
-    public void testGetPeakRefreshRate() {
-        float defaultPeakRefreshRate = 100;
-
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, -1);
-        assertEquals(defaultPeakRefreshRate,
-                RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate),
-                /* delta= */ 0);
-
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, 0);
-        assertEquals(DEFAULT_REFRESH_RATE,
-                RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate),
-                /* delta= */ 0);
-
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, 1);
-        assertEquals(120,
-                RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate),
-                /* delta= */ 0);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
new file mode 100644
index 0000000..eec026cc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.companion.datatransfer.contextsync;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.companion.transport.CompanionTransportManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+public class CrossDeviceSyncControllerTest {
+
+    private CrossDeviceSyncController mCrossDeviceSyncController;
+    @Mock
+    private CompanionTransportManager mMockCompanionTransportManager;
+    @Mock
+    private CrossDeviceCall mMockCrossDeviceCall;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mCrossDeviceSyncController = new CrossDeviceSyncController(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                mMockCompanionTransportManager);
+    }
+
+    @Test
+    public void processTelecomDataFromSync_createCallUpdateMessage_emptyCallsAndRequests() {
+        final byte[] data = mCrossDeviceSyncController.createCallUpdateMessage(new HashSet<>(),
+                InstrumentationRegistry.getInstrumentation().getContext().getUserId());
+        final CallMetadataSyncData callMetadataSyncData =
+                mCrossDeviceSyncController.processTelecomDataFromSync(data);
+        assertWithMessage("Unexpectedly found a call").that(
+                callMetadataSyncData.getCalls()).isEmpty();
+        assertWithMessage("Unexpectedly found a request").that(
+                callMetadataSyncData.getRequests()).isEmpty();
+    }
+
+    @Test
+    public void processTelecomDataFromSync_createEmptyMessage_emptyCallsAndRequests() {
+        final byte[] data = CrossDeviceSyncController.createEmptyMessage();
+        final CallMetadataSyncData callMetadataSyncData =
+                mCrossDeviceSyncController.processTelecomDataFromSync(data);
+        assertWithMessage("Unexpectedly found a call").that(
+                callMetadataSyncData.getCalls()).isEmpty();
+        assertWithMessage("Unexpectedly found a request").that(
+                callMetadataSyncData.getRequests()).isEmpty();
+    }
+
+    @Test
+    public void processTelecomDataFromSync_createCallUpdateMessage_hasCalls() {
+        when(mMockCrossDeviceCall.getId()).thenReturn(5L);
+        final String callerId = "Firstname Lastname";
+        when(mMockCrossDeviceCall.getReadableCallerId(anyBoolean())).thenReturn(callerId);
+        final String appName = "AppName";
+        when(mMockCrossDeviceCall.getCallingAppName()).thenReturn(appName);
+        final String appIcon = "ABCD";
+        when(mMockCrossDeviceCall.getCallingAppIcon()).thenReturn(appIcon.getBytes());
+        when(mMockCrossDeviceCall.getStatus()).thenReturn(android.companion.Telecom.Call.RINGING);
+        final Set<Integer> controls = Set.of(
+                android.companion.Telecom.Call.ACCEPT,
+                android.companion.Telecom.Call.REJECT,
+                android.companion.Telecom.Call.SILENCE);
+        when(mMockCrossDeviceCall.getControls()).thenReturn(controls);
+        final byte[] data = mCrossDeviceSyncController.createCallUpdateMessage(
+                new HashSet<>(List.of(mMockCrossDeviceCall)),
+                InstrumentationRegistry.getInstrumentation().getContext().getUserId());
+        final CallMetadataSyncData callMetadataSyncData =
+                mCrossDeviceSyncController.processTelecomDataFromSync(data);
+        assertWithMessage("Wrong number of active calls").that(
+                callMetadataSyncData.getCalls()).hasSize(1);
+        final CallMetadataSyncData.Call call =
+                callMetadataSyncData.getCalls().stream().findAny().orElseThrow();
+        assertWithMessage("Wrong id").that(call.getId()).isEqualTo(5L);
+        assertWithMessage("Wrong app icon").that(new String(call.getAppIcon())).isEqualTo(appIcon);
+        assertWithMessage("Wrong app name").that(call.getAppName()).isEqualTo(appName);
+        assertWithMessage("Wrong caller id").that(call.getCallerId()).isEqualTo(callerId);
+        assertWithMessage("Wrong status").that(call.getStatus())
+                .isEqualTo(android.companion.Telecom.Call.RINGING);
+        assertWithMessage("Wrong controls").that(call.getControls()).isEqualTo(controls);
+        assertWithMessage("Unexpectedly has requests").that(
+                callMetadataSyncData.getRequests()).isEmpty();
+    }
+
+    @Test
+    public void processTelecomDataFromMessage_createCallControlMessage_hasCallControlRequest() {
+        final byte[] data = CrossDeviceSyncController.createCallControlMessage(
+                /* callId= */ 5L, /* status= */ android.companion.Telecom.Call.ACCEPT);
+        final CallMetadataSyncData callMetadataSyncData =
+                mCrossDeviceSyncController.processTelecomDataFromSync(data);
+        assertWithMessage("Wrong number of requests").that(
+                callMetadataSyncData.getRequests()).hasSize(1);
+        final CallMetadataSyncData.Call call =
+                callMetadataSyncData.getRequests().stream().findAny().orElseThrow();
+        assertWithMessage("Wrong id").that(call.getId()).isEqualTo(5L);
+        assertWithMessage("Wrong app icon").that(call.getAppIcon()).isNull();
+        assertWithMessage("Wrong app name").that(call.getAppName()).isNull();
+        assertWithMessage("Wrong caller id").that(call.getCallerId()).isNull();
+        assertWithMessage("Wrong status").that(call.getStatus())
+                .isEqualTo(android.companion.Telecom.Call.UNKNOWN_STATUS);
+        assertWithMessage("Wrong controls").that(call.getControls())
+                .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT));
+        assertWithMessage("Unexpectedly has active calls").that(
+                callMetadataSyncData.getCalls()).isEmpty();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index e492252..b2a3a57 100644
--- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -84,7 +84,6 @@
 
 import com.android.internal.R;
 import com.android.internal.display.BrightnessSynchronizer;
-import com.android.internal.display.RefreshRateSettingsUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.test.FakeSettingsProvider;
@@ -159,9 +158,6 @@
         LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
-
-        clearSmoothDisplaySetting();
-        clearForcePeakRefreshRateSetting();
     }
 
     private DisplayModeDirector createDirectorFromRefreshRateArray(
@@ -922,6 +918,7 @@
     public void testLockFpsForLowZone() throws Exception {
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -929,7 +926,6 @@
         config.setRefreshRateInLowZone(90);
         config.setLowDisplayBrightnessThresholds(new int[] { 10 });
         config.setLowAmbientBrightnessThresholds(new int[] { 20 });
-        config.setDefaultPeakRefreshRate(90);
 
         Sensor lightSensor = createLightSensor();
         SensorManager sensorManager = createMockSensorManager(lightSensor);
@@ -980,6 +976,7 @@
     public void testLockFpsForHighZone() throws Exception {
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -987,7 +984,6 @@
         config.setRefreshRateInHighZone(60);
         config.setHighDisplayBrightnessThresholds(new int[] { 255 });
         config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
-        config.setDefaultPeakRefreshRate(90);
 
         Sensor lightSensor = createLightSensor();
         SensorManager sensorManager = createMockSensorManager(lightSensor);
@@ -1035,123 +1031,16 @@
     }
 
     @Test
-    public void testSmoothDisplay() {
-        float defaultRefreshRate = 60;
-        int defaultPeakRefreshRate = 100;
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        director.getSettingsObserver().setDefaultRefreshRate(defaultRefreshRate);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        final FakeDeviceConfig config = mInjector.getDeviceConfig();
-        config.setDefaultPeakRefreshRate(defaultPeakRefreshRate);
-
-        Sensor lightSensor = createLightSensor();
-        SensorManager sensorManager = createMockSensorManager(lightSensor);
-        director.start(sensorManager);
-
-        // Default value of the setting
-
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultPeakRefreshRate);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                Float.POSITIVE_INFINITY);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultRefreshRate);
-
-        setSmoothDisplayEnabled(false);
-
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                Float.POSITIVE_INFINITY);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultRefreshRate);
-
-        setSmoothDisplayEnabled(true);
-
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                Float.POSITIVE_INFINITY);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultRefreshRate);
-    }
-
-    @Test
-    public void testForcePeakRefreshRate() {
-        float defaultRefreshRate = 60;
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        director.getSettingsObserver().setDefaultRefreshRate(defaultRefreshRate);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        Sensor lightSensor = createLightSensor();
-        SensorManager sensorManager = createMockSensorManager(lightSensor);
-        director.start(sensorManager);
-
-        setForcePeakRefreshRateEnabled(false);
-        setSmoothDisplayEnabled(false);
-
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
-                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultRefreshRate);
-
-        setForcePeakRefreshRateEnabled(true);
-
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
-                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultRefreshRate);
-    }
-
-    @Test
     public void testSensorRegistration() {
         // First, configure brightness zones or DMD won't register for sensor data.
         final FakeDeviceConfig config = mInjector.getDeviceConfig();
         config.setRefreshRateInHighZone(60);
         config.setHighDisplayBrightnessThresholds(new int[] { 255 });
         config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
-        config.setDefaultPeakRefreshRate(90);
 
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -2527,10 +2416,10 @@
         config.setRefreshRateInHighZone(60);
         config.setHighDisplayBrightnessThresholds(new int[] { 255 });
         config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
-        config.setDefaultPeakRefreshRate(90);
 
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -2828,30 +2717,10 @@
         listener.onDisplayChanged(DISPLAY_ID);
     }
 
-    private void setSmoothDisplayEnabled(boolean enabled) {
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY,
-                enabled ? 1 : 0);
-        mInjector.notifySmoothDisplaySettingChanged();
-        waitForIdleSync();
-    }
-
-    private void clearSmoothDisplaySetting() {
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, -1);
-        mInjector.notifySmoothDisplaySettingChanged();
-        waitForIdleSync();
-    }
-
-    private void setForcePeakRefreshRateEnabled(boolean enabled) {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.FORCE_PEAK_REFRESH_RATE, enabled ? 1 : 0);
-        mInjector.notifyForcePeakRefreshRateSettingChanged();
-        waitForIdleSync();
-    }
-
-    private void clearForcePeakRefreshRateSetting() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.FORCE_PEAK_REFRESH_RATE, -1);
-        mInjector.notifySmoothDisplaySettingChanged();
+    private void setPeakRefreshRate(float fps) {
+        Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE,
+                 fps);
+        mInjector.notifyPeakRefreshRateChanged();
         waitForIdleSync();
     }
 
@@ -2900,8 +2769,7 @@
         private final Display mDisplay;
         private boolean mDisplayInfoValid = true;
         private ContentObserver mBrightnessObserver;
-        private ContentObserver mSmoothDisplaySettingObserver;
-        private ContentObserver mForcePeakRefreshRateSettingObserver;
+        private ContentObserver mPeakRefreshRateObserver;
 
         FakesInjector() {
             mDeviceConfig = new FakeDeviceConfig();
@@ -2918,15 +2786,9 @@
         }
 
         @Override
-        public void registerSmoothDisplayObserver(@NonNull ContentResolver cr,
+        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer) {
-            mSmoothDisplaySettingObserver = observer;
-        }
-
-        @Override
-        public void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer) {
-            mForcePeakRefreshRateSettingObserver = observer;
+            mPeakRefreshRateObserver = observer;
         }
 
         @Override
@@ -2976,17 +2838,10 @@
                     ApplicationProvider.getApplicationContext().getResources());
         }
 
-        void notifySmoothDisplaySettingChanged() {
-            if (mSmoothDisplaySettingObserver != null) {
-                mSmoothDisplaySettingObserver.dispatchChange(false /*selfChange*/,
-                        SMOOTH_DISPLAY_URI);
-            }
-        }
-
-        void notifyForcePeakRefreshRateSettingChanged() {
-            if (mForcePeakRefreshRateSettingObserver != null) {
-                mForcePeakRefreshRateSettingObserver.dispatchChange(false /*selfChange*/,
-                        FORCE_PEAK_REFRESH_RATE_URI);
+        void notifyPeakRefreshRateChanged() {
+            if (mPeakRefreshRateObserver != null) {
+                mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
+                        PEAK_REFRESH_RATE_URI);
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index a446e10..c53a7a7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION;
 import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER;
+import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_LONG;
 import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT;
 import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_SOURCE;
 import static com.android.server.hdmi.HdmiCecMessageValidator.OK;
@@ -145,11 +146,12 @@
     @Test
     public void isValid_systemAudioModeStatus() {
         assertMessageValidity("40:7E:00").isEqualTo(OK);
-        assertMessageValidity("40:7E:01:01").isEqualTo(OK);
+        assertMessageValidity("40:7E:01").isEqualTo(OK);
 
         assertMessageValidity("0F:7E:00").isEqualTo(ERROR_DESTINATION);
         assertMessageValidity("F0:7E").isEqualTo(ERROR_SOURCE);
         assertMessageValidity("40:7E").isEqualTo(ERROR_PARAMETER_SHORT);
+        assertMessageValidity("40:7E:01:1F:28").isEqualTo(ERROR_PARAMETER_LONG);
         assertMessageValidity("40:7E:02").isEqualTo(ERROR_PARAMETER);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 4dd5e94..fd6eb92 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -761,6 +761,13 @@
 
         assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
                 .isEqualTo(Constants.ABORT_INVALID_OPERAND);
+
+        // Validating ERROR_PARAMETER_LONG will generate ABORT_INVALID_OPERAND.
+        // Taken from HdmiCecMessageValidatorTest#isValid_systemAudioModeStatus
+        HdmiCecMessage systemAudioModeStatus = HdmiUtils.buildMessage("40:7E:01:1F:28");
+
+        assertThat(mHdmiControlServiceSpy.handleCecCommand(systemAudioModeStatus))
+                .isEqualTo(Constants.ABORT_INVALID_OPERAND);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index d07831d..fd65807 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -20,16 +20,25 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.Presubmit;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+@Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class InputMethodManagerServiceTests {
@@ -87,4 +96,25 @@
                 InputMethodManagerService.computeImeDisplayIdForTarget(
                         SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
     }
+
+    @Test
+    public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
+        var writer = new StringWriter();
+        var history = new InputMethodManagerService.SoftInputShowHideHistory();
+        history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
+                null,
+                null,
+                null,
+                SOFT_INPUT_STATE_UNSPECIFIED,
+                SoftInputShowHideReason.SHOW_SOFT_INPUT,
+                false,
+                null,
+                null,
+                null,
+                null));
+
+        history.dump(new PrintWriter(writer), "" /* prefix */);
+
+        // Asserts that dump doesn't throw an NPE.
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 397e3c1..539f329 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.Mockito.when;
 
 import android.Manifest;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
@@ -47,7 +48,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -67,7 +67,7 @@
 public class PermissionHelperTest extends UiServiceTestCase {
 
     @Mock
-    private PermissionManagerServiceInternal mPmi;
+    private Context mContext;
     @Mock
     private IPackageManager mPackageManager;
     @Mock
@@ -80,7 +80,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager);
+        mPermissionHelper = new PermissionHelper(mContext, mPackageManager, mPermManager);
         PackageInfo testPkgInfo = new PackageInfo();
         testPkgInfo.requestedPermissions = new String[]{ Manifest.permission.POST_NOTIFICATIONS };
         when(mPackageManager.getPackageInfo(anyString(), anyLong(), anyInt()))
@@ -89,12 +89,12 @@
 
     @Test
     public void testHasPermission() throws Exception {
-        when(mPmi.checkUidPermission(anyInt(), anyString()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
 
         assertThat(mPermissionHelper.hasPermission(1)).isTrue();
 
-        when(mPmi.checkUidPermission(anyInt(), anyString()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
 
         assertThat(mPermissionHelper.hasPermission(1)).isFalse();
@@ -241,7 +241,7 @@
 
     @Test
     public void testSetNotificationPermission_grantUserSet() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
         mPermissionHelper.setNotificationPermission("pkg", 10, true, true);
 
@@ -255,7 +255,7 @@
     @Test
     public void testSetNotificationPermission_pkgPerm_grantedByDefaultPermSet_allUserSet()
             throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
         when(mPermManager.getPermissionFlags(anyString(),
                 eq(Manifest.permission.POST_NOTIFICATIONS),
@@ -273,7 +273,7 @@
 
     @Test
     public void testSetNotificationPermission_revokeUserSet() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
 
         mPermissionHelper.setNotificationPermission("pkg", 10, false, true);
@@ -287,7 +287,7 @@
 
     @Test
     public void testSetNotificationPermission_grantNotUserSet() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
 
         mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
@@ -300,7 +300,7 @@
 
     @Test
     public void testSetNotificationPermission_revokeNotUserSet() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
 
         mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
@@ -340,7 +340,7 @@
 
     @Test
     public void testSetNotificationPermission_alreadyGrantedNotRegranted() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
         mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
 
@@ -350,7 +350,7 @@
 
     @Test
     public void testSetNotificationPermission_alreadyRevokedNotRerevoked() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
         mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
 
@@ -360,16 +360,19 @@
 
     @Test
     public void testSetNotificationPermission_doesntRequestNotChanged() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        int testUid = -1;
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
+        when(mPackageManager.getPackageUid(anyString(), anyInt(), anyInt()))
+                .thenReturn(testUid);
         PackageInfo testPkgInfo = new PackageInfo();
         testPkgInfo.requestedPermissions = new String[]{ Manifest.permission.RECORD_AUDIO };
         when(mPackageManager.getPackageInfo(anyString(), anyLong(), anyInt()))
                 .thenReturn(testPkgInfo);
         mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
 
-        verify(mPmi, never()).checkPermission(
-                eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10));
+        verify(mContext, never()).checkPermission(
+                eq(Manifest.permission.POST_NOTIFICATIONS), eq(-1), eq(testUid));
         verify(mPermManager, never()).revokeRuntimePermission(
                 eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index dfc453f..d173ce9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -1134,7 +1134,7 @@
         TaskChangeNotificationController controller = mAtm.getTaskChangeNotificationController();
         spyOn(controller);
         mWm.mRoot.lockAllProfileTasks(profileUserId);
-        verify(controller).notifyTaskProfileLocked(any());
+        verify(controller).notifyTaskProfileLocked(any(), eq(profileUserId));
 
         // Create the work lock activity on top of the task
         final ActivityRecord workLockActivity = new ActivityBuilder(mAtm).setTask(task).build();
@@ -1144,7 +1144,7 @@
         // Make sure the listener won't be notified again.
         clearInvocations(controller);
         mWm.mRoot.lockAllProfileTasks(profileUserId);
-        verify(controller, never()).notifyTaskProfileLocked(any());
+        verify(controller, never()).notifyTaskProfileLocked(any(), anyInt());
     }
 
     /**
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a98429a..ef13594 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2539,10 +2539,20 @@
         }
 
         @Override
-        public void reportChooserSelection(String packageName, int userId, String contentType,
-                                           String[] annotations, String action) {
+        public void reportChooserSelection(@NonNull String packageName, int userId,
+                @NonNull String contentType, String[] annotations, @NonNull String action) {
             if (packageName == null) {
-                Slog.w(TAG, "Event report user selecting a null package");
+                throw new IllegalArgumentException("Package selection must not be null.");
+            }
+            if (contentType == null) {
+                throw new IllegalArgumentException("Content type for selection must not be null.");
+            }
+            if (action == null) {
+                throw new IllegalArgumentException("Selection action must not be null.");
+            }
+            // Verify if this package exists before reporting an event for it.
+            if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) {
+                Slog.w(TAG, "Event report user selecting an invalid package");
                 return;
             }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 486945d..edaaf3f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -913,8 +913,10 @@
         }
         // Handle case where all hotword detector sessions are destroyed with only the visual
         // detector session left
-        if (mDetectorSessions.size() == 1
-                && mDetectorSessions.get(0) instanceof VisualQueryDetectorSession) {
+        boolean allHotwordDetectionServiceSessionsRemoved = mDetectorSessions.size() == 0
+                || (mDetectorSessions.size() == 1 && mDetectorSessions.get(0)
+                instanceof VisualQueryDetectorSession);
+        if (allHotwordDetectionServiceSessionsRemoved) {
             unbindHotwordDetectionService();
         }
     }