Merge "Remove FLAG_SPLIT_TOUCH from SystemUI" into main
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 93d751c..fbe5b94 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11854,7 +11854,7 @@
                     // If segment limit is exceeded. All segments will be replaced
                     // with a single segment
                     boolean allSameColor = true;
-                    int firstSegmentColor = segments.get(0).getColor();
+                    int firstSegmentColor = segments.getFirst().getColor();
 
                     for (int i = 1; i < segments.size(); i++) {
                         if (segments.get(i).getColor() != firstSegmentColor) {
@@ -11887,8 +11887,31 @@
                     }
                 }
 
+                // If the segments and points can't all fit inside the progress drawable, the
+                // view will replace all segments with a single segment.
+                final int segmentsFallbackColor;
+                if (segments.size() <= 1) {
+                    segmentsFallbackColor = NotificationProgressModel.INVALID_COLOR;
+                } else {
+
+                    boolean allSameColor = true;
+                    int firstSegmentColor = segments.getFirst().getColor();
+                    for (int i = 1; i < segments.size(); i++) {
+                        if (segments.get(i).getColor() != firstSegmentColor) {
+                            allSameColor = false;
+                            break;
+                        }
+                    }
+                    // If the segments are of the same color, the view can just use that color.
+                    // In that case there is no need to send the fallback color.
+                    segmentsFallbackColor = allSameColor ? NotificationProgressModel.INVALID_COLOR
+                            : sanitizeProgressColor(Notification.COLOR_DEFAULT, backgroundColor,
+                                    defaultProgressColor);
+                }
+
                 model = new NotificationProgressModel(segments, points,
-                        Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress);
+                        Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress,
+                        segmentsFallbackColor);
             }
             return model;
         }
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 76705dc..6936ddc 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -376,6 +376,14 @@
     }
 
     /**
+     * Returns the task id.
+     * @hide
+     */
+    public int getTaskId() {
+        return taskId;
+    }
+
+    /**
      * Whether this task is visible.
      */
     public boolean isVisible() {
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 67ad459..b54e17b 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1684,10 +1684,14 @@
             private IBinder mIBinder;
 
             ConnectionTask(@NonNull FilterComparison filter) {
-                mContext.bindService(filter.getIntent(),
-                        Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
-                        mHandler::post,
-                        this);
+                try {
+                    mContext.bindService(filter.getIntent(),
+                            Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+                            mHandler::post,
+                            this);
+                } catch (Exception e) {
+                    Log.e(TAG, "Error connecting to service in connection cache", e);
+                }
             }
 
             @Override
@@ -1737,7 +1741,11 @@
                     handleNext();
                     return;
                 }
-                mContext.unbindService(this);
+                try {
+                    mContext.unbindService(this);
+                } catch (Exception e) {
+                    Log.e(TAG, "Error unbinding the cached connection", e);
+                }
                 mActiveConnections.values().remove(this);
             }
         }
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 9d11710..8266384 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -85,6 +85,8 @@
     private static final boolean DEBUG = false;
     protected static final String REGISTERED_SERVICES_DIR = "registered_services";
 
+    static final long SERVICE_INFO_CACHES_TIMEOUT_MILLIS = 30000; // 30 seconds
+
     public final Context mContext;
     private final String mInterfaceName;
     private final String mMetaDataName;
@@ -96,8 +98,18 @@
     @GuardedBy("mServicesLock")
     private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
 
-    @GuardedBy("mServicesLock")
-    private final ArrayMap<String, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+    @GuardedBy("mServiceInfoCaches")
+    private final ArrayMap<ComponentName, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+
+    private final Handler mBackgroundHandler;
+
+    private final Runnable mClearServiceInfoCachesRunnable = new Runnable() {
+        public void run() {
+            synchronized (mServiceInfoCaches) {
+                mServiceInfoCaches.clear();
+            }
+        }
+    };
 
     private static class UserServices<V> {
         @GuardedBy("mServicesLock")
@@ -172,9 +184,9 @@
         if (isCore) {
             intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         }
-        Handler handler = BackgroundThread.getHandler();
+        mBackgroundHandler = BackgroundThread.getHandler();
         mContext.registerReceiverAsUser(
-                mPackageReceiver, UserHandle.ALL, intentFilter, null, handler);
+                mPackageReceiver, UserHandle.ALL, intentFilter, null, mBackgroundHandler);
 
         // Register for events related to sdcard installation.
         IntentFilter sdFilter = new IntentFilter();
@@ -183,7 +195,7 @@
         if (isCore) {
             sdFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         }
-        mContext.registerReceiver(mExternalReceiver, sdFilter, null, handler);
+        mContext.registerReceiver(mExternalReceiver, sdFilter, null, mBackgroundHandler);
 
         // Register for user-related events
         IntentFilter userFilter = new IntentFilter();
@@ -191,7 +203,7 @@
         if (isCore) {
             userFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         }
-        mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler);
+        mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, mBackgroundHandler);
     }
 
     private void handlePackageEvent(Intent intent, int userId) {
@@ -328,6 +340,10 @@
         public final ComponentName componentName;
         @UnsupportedAppUsage
         public final int uid;
+        /**
+         * The last update time of the package that contains the service.
+         * It's from {@link PackageInfo#lastUpdateTime}.
+         */
         public final long lastUpdateTime;
 
         /** @hide */
@@ -342,7 +358,8 @@
 
         @Override
         public String toString() {
-            return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
+            return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid
+                    + ", lastUpdateTime " + lastUpdateTime;
         }
     }
 
@@ -496,19 +513,56 @@
 
         final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
         final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
+        final PackageManager pm = mContext.getPackageManager();
         for (ResolveInfo resolveInfo : resolveInfos) {
+            // Check if the service has been in the service cache.
+            long lastUpdateTime = -1;
+            final android.content.pm.ServiceInfo si = resolveInfo.serviceInfo;
+            final ComponentName componentName = si.getComponentName();
+            if (Flags.optimizeParsingInRegisteredServicesCache()) {
+                try {
+                    PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
+                            PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+                    lastUpdateTime = packageInfo.lastUpdateTime;
+                } catch (NameNotFoundException | SecurityException e) {
+                    Slog.d(TAG, "Fail to get the PackageInfo in generateServicesMap: " + e);
+                    continue;
+                }
+                ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(componentName,
+                        lastUpdateTime);
+                if (serviceInfo != null) {
+                    serviceInfos.add(serviceInfo);
+                    continue;
+                }
+            }
             try {
-                ServiceInfo<V> info = parseServiceInfo(resolveInfo, userId);
+                ServiceInfo<V> info = parseServiceInfo(resolveInfo, lastUpdateTime);
                 if (info == null) {
                     Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
                     continue;
                 }
                 serviceInfos.add(info);
+                if (Flags.optimizeParsingInRegisteredServicesCache()) {
+                    synchronized (mServiceInfoCaches) {
+                        mServiceInfoCaches.put(componentName, info);
+                    }
+                }
             } catch (XmlPullParserException | IOException e) {
                 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
             }
         }
 
+        if (Flags.optimizeParsingInRegisteredServicesCache()) {
+            synchronized (mServiceInfoCaches) {
+                if (!mServiceInfoCaches.isEmpty()) {
+                    mBackgroundHandler.removeCallbacks(mClearServiceInfoCachesRunnable);
+                    mBackgroundHandler.postDelayed(mClearServiceInfoCachesRunnable,
+                            SERVICE_INFO_CACHES_TIMEOUT_MILLIS);
+                }
+            }
+        }
+
         synchronized (mServicesLock) {
             final UserServices<V> user = findOrCreateUserLocked(userId);
             final boolean firstScan = user.services == null;
@@ -645,32 +699,18 @@
         return false;
     }
 
+    /**
+     * If the service has already existed in the caches, this method will not be called to parse
+     * the service.
+     */
     @VisibleForTesting
-    protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, int userId)
+    protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, long lastUpdateTime)
             throws XmlPullParserException, IOException {
         android.content.pm.ServiceInfo si = service.serviceInfo;
         ComponentName componentName = new ComponentName(si.packageName, si.name);
 
         PackageManager pm = mContext.getPackageManager();
 
-        // Check if the service has been in the service cache.
-        long lastUpdateTime = -1;
-        if (Flags.optimizeParsingInRegisteredServicesCache()) {
-            try {
-                PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
-                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
-                lastUpdateTime = packageInfo.lastUpdateTime;
-
-                ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(si, lastUpdateTime);
-                if (serviceInfo != null) {
-                    return serviceInfo;
-                }
-            } catch (NameNotFoundException | SecurityException e) {
-                Slog.d(TAG, "Fail to get the PackageInfo in parseServiceInfo: " + e);
-            }
-        }
-
         XmlResourceParser parser = null;
         try {
             parser = si.loadXmlMetaData(pm, mMetaDataName);
@@ -696,13 +736,7 @@
             if (v == null) {
                 return null;
             }
-            ServiceInfo<V> serviceInfo = new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
-            if (Flags.optimizeParsingInRegisteredServicesCache()) {
-                synchronized (mServicesLock) {
-                    mServiceInfoCaches.put(getServiceCacheKey(si), serviceInfo);
-                }
-            }
-            return serviceInfo;
+            return new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
         } catch (NameNotFoundException e) {
             throw new XmlPullParserException(
                     "Unable to load resources for pacakge " + si.packageName);
@@ -873,26 +907,13 @@
         mContext.unregisterReceiver(mUserRemovedReceiver);
     }
 
-    private static String getServiceCacheKey(@NonNull android.content.pm.ServiceInfo serviceInfo) {
-        StringBuilder sb = new StringBuilder(serviceInfo.packageName);
-        sb.append('-');
-        sb.append(serviceInfo.name);
-        return sb.toString();
-    }
-
-    private ServiceInfo<V> getServiceInfoFromServiceCache(
-            @NonNull android.content.pm.ServiceInfo serviceInfo, long lastUpdateTime) {
-        String serviceCacheKey = getServiceCacheKey(serviceInfo);
-        synchronized (mServicesLock) {
-            ServiceInfo<V> serviceCache = mServiceInfoCaches.get(serviceCacheKey);
-            if (serviceCache == null) {
-                return null;
-            }
-            if (serviceCache.lastUpdateTime == lastUpdateTime) {
+    private ServiceInfo<V> getServiceInfoFromServiceCache(@NonNull ComponentName componentName,
+            long lastUpdateTime) {
+        synchronized (mServiceInfoCaches) {
+            ServiceInfo<V> serviceCache = mServiceInfoCaches.get(componentName);
+            if (serviceCache != null && serviceCache.lastUpdateTime == lastUpdateTime) {
                 return serviceCache;
             }
-            // The service is not latest, remove it from the cache.
-            mServiceInfoCaches.remove(serviceCacheKey);
             return null;
         }
     }
diff --git a/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
index 1268658..cf25b7e 100644
--- a/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
@@ -20,5 +20,5 @@
  * @hide
  */
 oneway interface IBiometricEnabledOnKeyguardCallback {
-    void onChanged(boolean enabled, int userId);
+    void onChanged(boolean enabled, int userId, int modality);
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index b380e25..cd48047 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -385,6 +385,42 @@
     }
 
     /**
+     * Whether touchpad acceleration is enabled or not.
+     *
+     * @param context The application context.
+     *
+     * @hide
+     */
+    public static boolean isTouchpadAccelerationEnabled(@NonNull Context context) {
+        if (!isPointerAccelerationFeatureFlagEnabled()) {
+            return false;
+        }
+
+        return Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.TOUCHPAD_ACCELERATION_ENABLED, 1, UserHandle.USER_CURRENT)
+                == 1;
+    }
+
+   /**
+    * Enables or disables touchpad acceleration.
+    *
+    * @param context The application context.
+    * @param enabled Will enable touchpad acceleration if true, disable it if
+    *                false.
+    * @hide
+    */
+    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+    public static void setTouchpadAccelerationEnabled(@NonNull Context context,
+            boolean enabled) {
+        if (!isPointerAccelerationFeatureFlagEnabled()) {
+            return;
+        }
+        Settings.System.putIntForUser(context.getContentResolver(),
+                Settings.System.TOUCHPAD_ACCELERATION_ENABLED, enabled ? 1 : 0,
+                UserHandle.USER_CURRENT);
+    }
+
+    /**
      * Returns true if the feature flag for disabling system gestures on touchpads is enabled.
      *
      * @hide
@@ -835,7 +871,6 @@
                 UserHandle.USER_CURRENT);
     }
 
-
     /**
      * Whether Accessibility bounce keys feature is enabled.
      *
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9626808..2e231e3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6330,6 +6330,17 @@
         public static final String TOUCHPAD_SYSTEM_GESTURES = "touchpad_system_gestures";
 
         /**
+         * Whether touchpad acceleration is enabled.
+         *
+         * When enabled, the speed of the pointer will increase as the user moves their
+         * finger faster on the touchpad.
+         *
+         * @hide
+         */
+        public static final String TOUCHPAD_ACCELERATION_ENABLED =
+                "touchpad_acceleration_enabled";
+
+        /**
          * Whether to enable reversed vertical scrolling for connected mice.
          *
          * When enabled, scrolling down on the mouse wheel will move the screen up and vice versa.
@@ -6624,6 +6635,7 @@
             PRIVATE_SETTINGS.add(TOUCHPAD_TAP_DRAGGING);
             PRIVATE_SETTINGS.add(TOUCHPAD_RIGHT_CLICK_ZONE);
             PRIVATE_SETTINGS.add(TOUCHPAD_SYSTEM_GESTURES);
+            PRIVATE_SETTINGS.add(TOUCHPAD_ACCELERATION_ENABLED);
             PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
             PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
             PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
@@ -11368,17 +11380,54 @@
 
         /**
          * Whether or not biometric is allowed on Keyguard.
+         *
+         * @deprecated Use {@link #FINGERPRINT_KEYGUARD_ENABLED} or {@link #FACE_KEYGUARD_ENABLED}
+         * instead.
+         *
          * @hide
          */
+        @Deprecated
         @Readable
         public static final String BIOMETRIC_KEYGUARD_ENABLED = "biometric_keyguard_enabled";
 
         /**
          * Whether or not biometric is allowed for apps (through BiometricPrompt).
+         *
+         * @deprecated Use {@link #FINGERPRINT_APP_ENABLED} or {@link #FACE_APP_ENABLED} instead.
+         *
+         * @hide
+         */
+        @Deprecated
+        @Readable
+        public static final String BIOMETRIC_APP_ENABLED = "biometric_app_enabled";
+
+        /**
+         * Whether or not fingerprint is allowed on Keyguard.
          * @hide
          */
         @Readable
-        public static final String BIOMETRIC_APP_ENABLED = "biometric_app_enabled";
+        public static final String FINGERPRINT_KEYGUARD_ENABLED = "fingerprint_keyguard_enabled";
+
+        /**
+         * Whether or not fingerprint is allowed for apps (through BiometricPrompt).
+         * @hide
+         */
+        @Readable
+        public static final String FINGERPRINT_APP_ENABLED = "fingerptint_app_enabled";
+
+        /**
+         * Whether or not face is allowed on Keyguard.
+         * @hide
+         */
+        @Readable
+        public static final String FACE_KEYGUARD_ENABLED = "face_keyguard_enabled";
+
+        /**
+         * Whether or not face is allowed for apps (through BiometricPrompt).
+         * @hide
+         */
+        @Readable
+        public static final String FACE_APP_ENABLED = "face_app_enabled";
 
         /**
          * Whether or not mandatory biometrics is enabled.
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 0d3c470..973fd7e 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -47,6 +47,7 @@
 import com.android.internal.widget.NotificationProgressDrawable.DrawableSegment;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -161,7 +162,7 @@
             final int progress = mProgressModel.getProgress();
             final int progressMax = mProgressModel.getProgressMax();
 
-            mParts = processAndConvertToViewParts(mProgressModel.getSegments(),
+            mParts = processModelAndConvertToViewParts(mProgressModel.getSegments(),
                     mProgressModel.getPoints(),
                     progress,
                     progressMax);
@@ -439,23 +440,107 @@
             return;
         }
 
-        mProgressDrawableParts = processAndConvertToDrawableParts(
+        final float segSegGap = mNotificationProgressDrawable.getSegSegGap();
+        final float segPointGap = mNotificationProgressDrawable.getSegPointGap();
+        final float pointRadius = mNotificationProgressDrawable.getPointRadius();
+        mProgressDrawableParts = processPartsAndConvertToDrawableParts(
                 mParts,
                 width,
-                mNotificationProgressDrawable.getSegSegGap(),
-                mNotificationProgressDrawable.getSegPointGap(),
-                mNotificationProgressDrawable.getPointRadius(),
+                segSegGap,
+                segPointGap,
+                pointRadius,
                 mHasTrackerIcon
         );
-        Pair<List<DrawablePart>, Float> p = maybeStretchAndRescaleSegments(
-                mParts,
-                mProgressDrawableParts,
-                mNotificationProgressDrawable.getSegmentMinWidth(),
-                mNotificationProgressDrawable.getPointRadius(),
-                getProgressFraction(),
-                width,
-                mProgressModel.isStyledByProgress(),
-                mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap());
+
+        final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth();
+        final float progressFraction = getProgressFraction();
+        final boolean isStyledByProgress = mProgressModel.isStyledByProgress();
+        final float progressGap =
+                mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap();
+        Pair<List<DrawablePart>, Float> p = null;
+        try {
+            p = maybeStretchAndRescaleSegments(
+                    mParts,
+                    mProgressDrawableParts,
+                    segmentMinWidth,
+                    pointRadius,
+                    progressFraction,
+                    width,
+                    isStyledByProgress,
+                    progressGap
+            );
+        } catch (NotEnoughWidthToFitAllPartsException ex) {
+            Log.w(TAG, "Failed to stretch and rescale segments", ex);
+        }
+
+        List<ProgressStyle.Segment> fallbackSegments = null;
+        if (p == null && mProgressModel.getSegments().size() > 1) {
+            Log.w(TAG, "Falling back to single segment");
+            try {
+                fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
+                        mProgressModel.getSegmentsFallbackColor()
+                                == NotificationProgressModel.INVALID_COLOR
+                                ? mProgressModel.getSegments().getFirst().getColor()
+                                : mProgressModel.getSegmentsFallbackColor()));
+                p = processModelAndConvertToFinalDrawableParts(
+                        fallbackSegments,
+                        mProgressModel.getPoints(),
+                        mProgressModel.getProgress(),
+                        getMax(),
+                        width,
+                        segSegGap,
+                        segPointGap,
+                        pointRadius,
+                        mHasTrackerIcon,
+                        segmentMinWidth,
+                        isStyledByProgress
+                );
+            } catch (NotEnoughWidthToFitAllPartsException ex) {
+                Log.w(TAG, "Failed to stretch and rescale segments with single segment fallback",
+                        ex);
+            }
+        }
+
+        if (p == null && !mProgressModel.getPoints().isEmpty()) {
+            Log.w(TAG, "Falling back to single segment and no points");
+            if (fallbackSegments == null) {
+                fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
+                        mProgressModel.getSegmentsFallbackColor()
+                                == NotificationProgressModel.INVALID_COLOR
+                                ? mProgressModel.getSegments().getFirst().getColor()
+                                : mProgressModel.getSegmentsFallbackColor()));
+            }
+            try {
+                p = processModelAndConvertToFinalDrawableParts(
+                        fallbackSegments,
+                        Collections.emptyList(),
+                        mProgressModel.getProgress(),
+                        getMax(),
+                        width,
+                        segSegGap,
+                        segPointGap,
+                        pointRadius,
+                        mHasTrackerIcon,
+                        segmentMinWidth,
+                        isStyledByProgress
+                );
+            } catch (NotEnoughWidthToFitAllPartsException ex) {
+                Log.w(TAG,
+                        "Failed to stretch and rescale segments with single segments and no points",
+                        ex);
+            }
+        }
+
+        if (p == null) {
+            Log.w(TAG, "Falling back to no stretching and rescaling");
+            p = maybeSplitDrawableSegmentsByProgress(
+                    mParts,
+                    mProgressDrawableParts,
+                    progressFraction,
+                    width,
+                    isStyledByProgress,
+                    progressGap);
+        }
 
         if (DEBUG) {
             Log.d(TAG, "Updating NotificationProgressDrawable parts");
@@ -502,7 +587,11 @@
         int min = getMin();
         int max = getMax();
         int range = max - min;
-        return range > 0 ? (getProgress() - min) / (float) range : 0;
+        return getProgressFraction(range, (getProgress() - min));
+    }
+
+    private static float getProgressFraction(int progressMax, int progress) {
+        return progressMax > 0 ? progress / (float) progressMax : 0;
     }
 
     /**
@@ -636,7 +725,7 @@
      * Processes the ProgressStyle data and convert to a list of {@code Part}.
      */
     @VisibleForTesting
-    public static List<Part> processAndConvertToViewParts(
+    public static List<Part> processModelAndConvertToViewParts(
             List<ProgressStyle.Segment> segments,
             List<ProgressStyle.Point> points,
             int progress,
@@ -796,7 +885,7 @@
      * Processes the list of {@code Part} and convert to a list of {@code DrawablePart}.
      */
     @VisibleForTesting
-    public static List<DrawablePart> processAndConvertToDrawableParts(
+    public static List<DrawablePart> processPartsAndConvertToDrawableParts(
             List<Part> parts,
             float totalWidth,
             float segSegGap,
@@ -823,7 +912,7 @@
                 // Retract the end position to account for the padding and a point immediately
                 // after.
                 final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
-                        segSegGap, iPart == nParts - 2, totalWidth, hasTrackerIcon);
+                        segSegGap, iPart == nParts - 2, hasTrackerIcon);
                 final float end = x + segWidth - endOffset;
 
                 drawableParts.add(new DrawableSegment(start, end, segment.mColor, segment.mFaded));
@@ -864,7 +953,7 @@
     }
 
     private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
-            float segPointGap, float segSegGap, boolean isSecondToLastPart, float totalWidth,
+            float segPointGap, float segSegGap, boolean isSecondToLastPart,
             boolean hasTrackerIcon) {
         if (nextPart == null) return 0F;
         if (nextPart instanceof Segment nextSeg) {
@@ -894,7 +983,7 @@
             float totalWidth,
             boolean isStyledByProgress,
             float progressGap
-    ) {
+    ) throws NotEnoughWidthToFitAllPartsException {
         final List<DrawableSegment> drawableSegments = drawableParts
                 .stream()
                 .filter(DrawableSegment.class::isInstance)
@@ -920,16 +1009,8 @@
         }
 
         if (totalExcessWidth < 0) {
-            // TODO: b/372908709 - throw an error so that the caller can catch and go to fallback
-            //  option. (instead of return.)
-            Log.w(TAG, "Not enough width to satisfy the minimum width for segments.");
-            return maybeSplitDrawableSegmentsByProgress(
-                    parts,
-                    drawableParts,
-                    progressFraction,
-                    totalWidth,
-                    isStyledByProgress,
-                    progressGap);
+            throw new NotEnoughWidthToFitAllPartsException(
+                    "Not enough width to satisfy the minimum width for segments.");
         }
 
         final int nParts = drawableParts.size();
@@ -1003,8 +1084,7 @@
         final int nParts = parts.size();
         for (int iPart = 0; iPart < nParts; iPart++) {
             final Part part = parts.get(iPart);
-            if (!(part instanceof Segment)) continue;
-            final Segment segment = (Segment) part;
+            if (!(part instanceof Segment segment)) continue;
             if (startFraction == progressFraction) {
                 iPartFirstSegmentToStyle = iPart;
                 rescaledProgressX = segment.mStart;
@@ -1066,11 +1146,37 @@
     }
 
     /**
+     * Processes the ProgressStyle data and convert to a pair of:
+     * - list of processed {@code DrawablePart}.
+     * - location of progress on the stretched and rescaled progress bar.
+     */
+    @VisibleForTesting
+    public static Pair<List<DrawablePart>, Float> processModelAndConvertToFinalDrawableParts(
+            List<ProgressStyle.Segment> segments,
+            List<ProgressStyle.Point> points,
+            int progress,
+            int progressMax,
+            float totalWidth,
+            float segSegGap,
+            float segPointGap,
+            float pointRadius,
+            boolean hasTrackerIcon,
+            float segmentMinWidth,
+            boolean isStyledByProgress
+    ) throws NotEnoughWidthToFitAllPartsException {
+        List<Part> parts = processModelAndConvertToViewParts(segments, points, progress,
+                progressMax);
+        List<DrawablePart> drawableParts = processPartsAndConvertToDrawableParts(parts, totalWidth,
+                segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        return maybeStretchAndRescaleSegments(parts, drawableParts, segmentMinWidth, pointRadius,
+                getProgressFraction(progressMax, progress), totalWidth, isStyledByProgress,
+                hasTrackerIcon ? 0F : segSegGap);
+    }
+
+    /**
      * A part of the progress bar, which is either a {@link Segment} with non-zero length, or a
      * {@link Point} with zero length.
      */
-    // TODO: b/372908709 - maybe this should be made private? Only test the final
-    //  NotificationDrawable.Parts.
     public interface Part {
     }
 
@@ -1176,4 +1282,10 @@
             return Objects.hash(mColor);
         }
     }
+
+    public static class NotEnoughWidthToFitAllPartsException extends Exception {
+        public NotEnoughWidthToFitAllPartsException(String message) {
+            super(message);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/NotificationProgressModel.java b/core/java/com/android/internal/widget/NotificationProgressModel.java
index e8cb37e..7eaf861 100644
--- a/core/java/com/android/internal/widget/NotificationProgressModel.java
+++ b/core/java/com/android/internal/widget/NotificationProgressModel.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.widget;
 
-
 import android.annotation.ColorInt;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
@@ -45,24 +44,29 @@
  */
 @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
 public final class NotificationProgressModel {
-    private static final int INVALID_INDETERMINATE_COLOR = Color.TRANSPARENT;
+    public static final int INVALID_COLOR = Color.TRANSPARENT;
     private static final String KEY_SEGMENTS = "segments";
     private static final String KEY_POINTS = "points";
     private static final String KEY_PROGRESS = "progress";
     private static final String KEY_IS_STYLED_BY_PROGRESS = "isStyledByProgress";
+    private static final String KEY_SEGMENTS_FALLBACK_COLOR = "segmentsFallColor";
     private static final String KEY_INDETERMINATE_COLOR = "indeterminateColor";
     private final List<Segment> mSegments;
     private final List<Point> mPoints;
     private final int mProgress;
     private final boolean mIsStyledByProgress;
     @ColorInt
+    private final int mSegmentsFallbackColor;
+
+    @ColorInt
     private final int mIndeterminateColor;
 
     public NotificationProgressModel(
             @NonNull List<Segment> segments,
             @NonNull List<Point> points,
             int progress,
-            boolean isStyledByProgress
+            boolean isStyledByProgress,
+            @ColorInt int segmentsFallbackColor
     ) {
         Preconditions.checkArgument(progress >= 0);
         Preconditions.checkArgument(!segments.isEmpty());
@@ -70,17 +74,19 @@
         mPoints = points;
         mProgress = progress;
         mIsStyledByProgress = isStyledByProgress;
-        mIndeterminateColor = INVALID_INDETERMINATE_COLOR;
+        mSegmentsFallbackColor = segmentsFallbackColor;
+        mIndeterminateColor = INVALID_COLOR;
     }
 
     public NotificationProgressModel(
             @ColorInt int indeterminateColor
     ) {
-        Preconditions.checkArgument(indeterminateColor != INVALID_INDETERMINATE_COLOR);
+        Preconditions.checkArgument(indeterminateColor != INVALID_COLOR);
         mSegments = Collections.emptyList();
         mPoints = Collections.emptyList();
         mProgress = 0;
         mIsStyledByProgress = false;
+        mSegmentsFallbackColor = INVALID_COLOR;
         mIndeterminateColor = indeterminateColor;
     }
 
@@ -105,12 +111,17 @@
     }
 
     @ColorInt
+    public int getSegmentsFallbackColor() {
+        return mSegmentsFallbackColor;
+    }
+
+    @ColorInt
     public int getIndeterminateColor() {
         return mIndeterminateColor;
     }
 
     public boolean isIndeterminate() {
-        return mIndeterminateColor != INVALID_INDETERMINATE_COLOR;
+        return mIndeterminateColor != INVALID_COLOR;
     }
 
     /**
@@ -119,7 +130,7 @@
     @NonNull
     public Bundle toBundle() {
         final Bundle bundle = new Bundle();
-        if (mIndeterminateColor != INVALID_INDETERMINATE_COLOR) {
+        if (mIndeterminateColor != INVALID_COLOR) {
             bundle.putInt(KEY_INDETERMINATE_COLOR, mIndeterminateColor);
         } else {
             bundle.putParcelableList(KEY_SEGMENTS,
@@ -128,6 +139,9 @@
                     Notification.ProgressStyle.getProgressPointsAsBundleList(mPoints));
             bundle.putInt(KEY_PROGRESS, mProgress);
             bundle.putBoolean(KEY_IS_STYLED_BY_PROGRESS, mIsStyledByProgress);
+            if (mSegmentsFallbackColor != INVALID_COLOR) {
+                bundle.putInt(KEY_SEGMENTS_FALLBACK_COLOR, mSegmentsFallbackColor);
+            }
         }
         return bundle;
     }
@@ -138,8 +152,8 @@
     @NonNull
     public static NotificationProgressModel fromBundle(@NonNull Bundle bundle) {
         final int indeterminateColor = bundle.getInt(KEY_INDETERMINATE_COLOR,
-                INVALID_INDETERMINATE_COLOR);
-        if (indeterminateColor != INVALID_INDETERMINATE_COLOR) {
+                INVALID_COLOR);
+        if (indeterminateColor != INVALID_COLOR) {
             return new NotificationProgressModel(indeterminateColor);
         } else {
             final List<Segment> segments =
@@ -150,7 +164,10 @@
                             bundle.getParcelableArrayList(KEY_POINTS, Bundle.class));
             final int progress = bundle.getInt(KEY_PROGRESS);
             final boolean isStyledByProgress = bundle.getBoolean(KEY_IS_STYLED_BY_PROGRESS);
-            return new NotificationProgressModel(segments, points, progress, isStyledByProgress);
+            final int segmentsFallbackColor = bundle.getInt(KEY_SEGMENTS_FALLBACK_COLOR,
+                    INVALID_COLOR);
+            return new NotificationProgressModel(segments, points, progress, isStyledByProgress,
+                    segmentsFallbackColor);
         }
     }
 
@@ -161,6 +178,7 @@
                 + ", mPoints=" + mPoints
                 + ", mProgress=" + mProgress
                 + ", mIsStyledByProgress=" + mIsStyledByProgress
+                + ", mSegmentsFallbackColor=" + mSegmentsFallbackColor
                 + ", mIndeterminateColor=" + mIndeterminateColor + "}";
     }
 
@@ -171,6 +189,7 @@
         final NotificationProgressModel that = (NotificationProgressModel) o;
         return mProgress == that.mProgress
                 && mIsStyledByProgress == that.mIsStyledByProgress
+                && mSegmentsFallbackColor == that.mSegmentsFallbackColor
                 && mIndeterminateColor == that.mIndeterminateColor
                 && Objects.equals(mSegments, that.mSegments)
                 && Objects.equals(mPoints, that.mPoints);
@@ -182,6 +201,7 @@
                 mPoints,
                 mProgress,
                 mIsStyledByProgress,
+                mSegmentsFallbackColor,
                 mIndeterminateColor);
     }
 }
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 64c9f54..325790c 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -218,7 +218,8 @@
         optional SettingProto tap_to_click = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto tap_dragging = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto three_finger_tap_customization = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
-        optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];;
+        optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto acceleration_enabled = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];;
     }
     optional Touchpad touchpad = 36;
 
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index bd1d303..a1f85c3 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7585,7 +7585,7 @@
     <!-- Used to config the segments of a NotificationProgressDrawable. -->
     <!-- @hide internal use only -->
     <declare-styleable name="NotificationProgressDrawableSegments">
-        <!-- TODO: b/372908709 - maybe move this to NotificationProgressBar, because that's the only
+        <!-- TODO: b/390196782 - maybe move this to NotificationProgressBar, because that's the only
          place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap
          above. -->
         <!-- Minimum required drawing width. The drawing width refers to the width after
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 939bf2e..ee4761b 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -76,6 +76,10 @@
         mUsers = new ArrayList<>();
         mUsers.add(new UserInfo(0, "Owner", UserInfo.FLAG_ADMIN));
         mUsers.add(new UserInfo(1, "User1", 0));
+        addServiceInfoIntoResolveInfo(r1, "r1.package.name" /* packageName */,
+                "r1.service.name" /* serviceName */);
+        addServiceInfoIntoResolveInfo(r2, "r2.package.name" /* packageName */,
+                "r2.service.name" /* serviceName */);
     }
 
     public void testGetAllServicesHappyPath() {
@@ -218,6 +222,14 @@
         assertTrue("File should be created at " + file, file.length() > 0);
     }
 
+    private void addServiceInfoIntoResolveInfo(ResolveInfo resolveInfo, String packageName,
+            String serviceName) {
+        final ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.packageName = packageName;
+        serviceInfo.name = serviceName;
+        resolveInfo.serviceInfo = serviceInfo;
+    }
+
     /**
      * Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
      */
@@ -301,8 +313,8 @@
         }
 
         @Override
-        protected ServiceInfo<TestServiceType> parseServiceInfo(
-                ResolveInfo resolveInfo, int userId) throws XmlPullParserException, IOException {
+        protected ServiceInfo<TestServiceType> parseServiceInfo(ResolveInfo resolveInfo,
+                long lastUpdateTime) throws XmlPullParserException, IOException {
             int size = mServices.size();
             for (int i = 0; i < size; i++) {
                 Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index 9818e19..ec19c0c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.widget;
 
+import static com.android.internal.widget.NotificationProgressBar.NotEnoughWidthToFitAllPartsException;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.Notification.ProgressStyle;
@@ -35,6 +37,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
@@ -47,7 +50,7 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -60,7 +63,7 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -73,7 +76,7 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -86,7 +89,7 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -98,20 +101,21 @@
         int progress = -50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
     @Test
-    public void processAndConvertToParts_progressIsZero() {
+    public void processAndConvertToParts_progressIsZero()
+            throws NotificationProgressBar.NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 0;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
 
@@ -123,8 +127,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 300, Color.RED)));
@@ -148,15 +153,16 @@
     }
 
     @Test
-    public void processAndConvertToParts_progressAtMax() {
+    public void processAndConvertToParts_progressAtMax()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 100;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
 
@@ -168,8 +174,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 300, Color.RED)));
@@ -195,7 +202,7 @@
         int progress = 150;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -208,7 +215,7 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -221,12 +228,62 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithoutPoints() {
+    public void processAndConvertToParts_singleSegmentWithoutPoints()
+            throws NotEnoughWidthToFitAllPartsException {
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+        List<ProgressStyle.Point> points = new ArrayList<>();
+        int progress = 60;
+        int progressMax = 100;
+
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
+
+        List<Part> expectedParts = new ArrayList<>(
+                List.of(new Segment(1, Color.BLUE)));
+
+        assertThat(parts).isEqualTo(expectedParts);
+
+        float drawableWidth = 300;
+        float segSegGap = 4;
+        float segPointGap = 4;
+        float pointRadius = 6;
+        boolean hasTrackerIcon = true;
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(0, 300, Color.BLUE)));
+
+        assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+        float segmentMinWidth = 16;
+        boolean isStyledByProgress = true;
+
+        Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+                parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+        // Colors with 40% opacity
+        int fadedBlue = 0x660000FF;
+        expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(0, 180, Color.BLUE),
+                        new DrawableSegment(180, 300, fadedBlue, true)));
+
+        assertThat(p.second).isEqualTo(180);
+        assertThat(p.first).isEqualTo(expectedDrawableParts);
+    }
+
+    @Test
+    public void processAndConvertToParts_multipleSegmentsWithoutPoints()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -234,8 +291,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN)));
@@ -248,8 +305,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 146, Color.RED),
@@ -274,15 +332,16 @@
     }
 
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker() {
+    public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 60;
         int progressMax = 100;
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN)));
@@ -295,8 +354,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = false;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 146, Color.RED),
@@ -321,7 +381,8 @@
     }
 
     @Test
-    public void processAndConvertToParts_singleSegmentWithPoints() {
+    public void processAndConvertToParts_singleSegmentWithPoints()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
         List<ProgressStyle.Point> points = new ArrayList<>();
@@ -332,8 +393,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.15f, Color.BLUE),
@@ -354,8 +415,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 35, Color.BLUE),
@@ -396,7 +458,8 @@
     }
 
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithPoints() {
+    public void processAndConvertToParts_multipleSegmentsWithPoints()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -408,8 +471,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.15f, Color.RED),
@@ -430,8 +493,9 @@
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
@@ -473,7 +537,8 @@
     }
 
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd() {
+    public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -485,8 +550,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Point(Color.RED),
@@ -505,8 +570,10 @@
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawablePoint(0, 12, Color.RED),
@@ -547,7 +614,8 @@
     // The points are so close to start/end that they would go out of bounds without the minimum
     // segment width requirement.
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithPointsNearStartAndEnd() {
+    public void processAndConvertToParts_multipleSegmentsWithPointsNearStartAndEnd()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -559,8 +627,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.01f, Color.RED),
@@ -581,8 +649,10 @@
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, -7, Color.RED),
@@ -625,7 +695,8 @@
     }
 
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress() {
+    public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -636,8 +707,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.15f, Color.RED), new Point(Color.RED),
@@ -653,8 +724,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
@@ -691,7 +763,8 @@
     // The only difference from the `zeroWidthDrawableSegment` test below is the longer
     // segmentMinWidth (= 16dp).
     @Test
-    public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment() {
+    public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
@@ -702,8 +775,8 @@
         int progress = 1000;
         int progressMax = 1000;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
@@ -717,8 +790,10 @@
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -749,7 +824,8 @@
     // The only difference from the `negativeWidthDrawableSegment` test above is the shorter
     // segmentMinWidth (= 10dp).
     @Test
-    public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment() {
+    public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
@@ -760,8 +836,8 @@
         int progress = 1000;
         int progressMax = 1000;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
@@ -775,8 +851,10 @@
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -805,7 +883,8 @@
     }
 
     @Test
-    public void maybeStretchAndRescaleSegments_noStretchingNecessary() {
+    public void maybeStretchAndRescaleSegments_noStretchingNecessary()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
@@ -816,8 +895,8 @@
         int progress = 1000;
         int progressMax = 1000;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Point(Color.BLUE), new Segment(0.2f, Color.BLUE),
@@ -832,8 +911,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -854,4 +934,168 @@
         assertThat(p.second).isEqualTo(200);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
+
+    @Test(expected = NotEnoughWidthToFitAllPartsException.class)
+    public void maybeStretchAndRescaleSegments_notEnoughWidthToFitAllParts()
+            throws NotEnoughWidthToFitAllPartsException {
+        final int orange = 0xff7f50;
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(10).setColor(orange));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.YELLOW));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.BLUE));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
+        segments.add(new ProgressStyle.Segment(10).setColor(orange));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.YELLOW));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.BLUE));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
+        List<ProgressStyle.Point> points = new ArrayList<>();
+        points.add(new ProgressStyle.Point(0).setColor(orange));
+        points.add(new ProgressStyle.Point(1).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(55).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(100).setColor(orange));
+        int progress = 50;
+        int progressMax = 100;
+
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
+
+        List<Part> expectedParts = new ArrayList<>(
+                List.of(new Point(orange),
+                        new Segment(0.01f, orange),
+                        new Point(Color.BLUE),
+                        new Segment(0.09f, orange),
+                        new Segment(0.1f, Color.YELLOW),
+                        new Segment(0.1f, Color.BLUE),
+                        new Segment(0.1f, Color.GREEN),
+                        new Segment(0.1f, Color.RED),
+                        new Segment(0.05f, orange),
+                        new Point(Color.BLUE),
+                        new Segment(0.05f, orange),
+                        new Segment(0.1f, Color.YELLOW),
+                        new Segment(0.1f, Color.BLUE),
+                        new Segment(0.1f, Color.GREEN),
+                        new Segment(0.1f, Color.RED),
+                        new Point(orange)));
+
+        assertThat(parts).isEqualTo(expectedParts);
+
+        // For the list of ProgressStyle.Part used in this test, 300 is the minimum width.
+        float drawableWidth = 299;
+        float segSegGap = 4;
+        float segPointGap = 4;
+        float pointRadius = 6;
+        boolean hasTrackerIcon = true;
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        // Skips the validation of the intermediate list of DrawableParts.
+
+        float segmentMinWidth = 16;
+        boolean isStyledByProgress = true;
+
+        NotificationProgressBar.maybeStretchAndRescaleSegments(
+                parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+    }
+
+    @Test
+    public void processModelAndConvertToFinalDrawableParts_singleSegmentWithPoints()
+            throws NotEnoughWidthToFitAllPartsException {
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+        List<ProgressStyle.Point> points = new ArrayList<>();
+        points.add(new ProgressStyle.Point(15).setColor(Color.RED));
+        points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
+        int progress = 60;
+        int progressMax = 100;
+
+        float drawableWidth = 300;
+        float segSegGap = 4;
+        float segPointGap = 4;
+        float pointRadius = 6;
+        boolean hasTrackerIcon = true;
+
+        float segmentMinWidth = 16;
+        boolean isStyledByProgress = true;
+
+        Pair<List<DrawablePart>, Float> p =
+                NotificationProgressBar.processModelAndConvertToFinalDrawableParts(
+                        segments,
+                        points,
+                        progress,
+                        progressMax,
+                        drawableWidth,
+                        segSegGap,
+                        segPointGap,
+                        pointRadius,
+                        hasTrackerIcon,
+                        segmentMinWidth,
+                        isStyledByProgress
+                );
+
+        // Colors with 40% opacity
+        int fadedBlue = 0x660000FF;
+        int fadedYellow = 0x66FFFF00;
+        List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
+                        new DrawablePoint(38.219177F, 50.219177F, Color.RED),
+                        new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
+                        new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
+                        new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
+                        new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
+                        new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
+                        new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
+                        new DrawableSegment(237.0137F, 300F, fadedBlue, true)));
+
+        assertThat(p.second).isEqualTo(182.38356F);
+        assertThat(p.first).isEqualTo(expectedDrawableParts);
+    }
+
+    @Test
+    public void processModelAndConvertToFinalDrawableParts_singleSegmentWithoutPoints()
+            throws NotEnoughWidthToFitAllPartsException {
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+        int progress = 60;
+        int progressMax = 100;
+
+        float drawableWidth = 100;
+        float segSegGap = 4;
+        float segPointGap = 4;
+        float pointRadius = 6;
+        boolean hasTrackerIcon = true;
+
+        float segmentMinWidth = 16;
+        boolean isStyledByProgress = true;
+
+        Pair<List<DrawablePart>, Float> p =
+                NotificationProgressBar.processModelAndConvertToFinalDrawableParts(
+                        segments,
+                        Collections.emptyList(),
+                        progress,
+                        progressMax,
+                        drawableWidth,
+                        segSegGap,
+                        segPointGap,
+                        pointRadius,
+                        hasTrackerIcon,
+                        segmentMinWidth,
+                        isStyledByProgress
+                );
+
+        // Colors with 40% opacity
+        int fadedBlue = 0x660000FF;
+        List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(0, 60.000004F, Color.BLUE),
+                        new DrawableSegment(60.000004F, 100, fadedBlue, true)));
+
+        assertThat(p.second).isWithin(1e-5f).of(60);
+        assertThat(p.first).isEqualTo(expectedDrawableParts);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
index 962399e..e1f5b1c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
@@ -49,7 +49,8 @@
         new NotificationProgressModel(List.of(),
                 List.of(),
                 10,
-                false);
+                false,
+                Color.TRANSPARENT);
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -58,7 +59,8 @@
                 List.of(new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW)),
                 List.of(),
                 -1,
-                false);
+                false,
+                Color.TRANSPARENT);
     }
 
     @Test
@@ -74,14 +76,15 @@
         // THEN
         assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.RED);
         assertThat(restoredModel.isIndeterminate()).isTrue();
-        assertThat(restoredModel.getProgress()).isEqualTo(-1);
+        assertThat(restoredModel.getProgress()).isEqualTo(0);
         assertThat(restoredModel.getSegments()).isEmpty();
         assertThat(restoredModel.getPoints()).isEmpty();
         assertThat(restoredModel.isStyledByProgress()).isFalse();
+        assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.TRANSPARENT);
     }
 
     @Test
-    public void save_and_restore_non_indeterminate_progress_model() {
+    public void save_and_restore_determinate_progress_model() {
         // GIVEN
         final List<Notification.ProgressStyle.Segment> segments = List.of(
                 new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
@@ -92,7 +95,8 @@
         final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
                 points,
                 100,
-                true);
+                true,
+                Color.RED);
 
         final Bundle bundle = savedModel.toBundle();
 
@@ -106,6 +110,38 @@
         assertThat(restoredModel.getPoints()).isEqualTo(points);
         assertThat(restoredModel.getProgress()).isEqualTo(100);
         assertThat(restoredModel.isStyledByProgress()).isTrue();
-        assertThat(restoredModel.getIndeterminateColor()).isEqualTo(-1);
+        assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.RED);
+        assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.TRANSPARENT);
+    }
+
+    @Test
+    public void save_and_restore_non_determinate_progress_model_segments_fallback_color_invalid() {
+        // GIVEN
+        final List<Notification.ProgressStyle.Segment> segments = List.of(
+                new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
+                new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW));
+        final List<Notification.ProgressStyle.Point> points = List.of(
+                new Notification.ProgressStyle.Point(0).setColor(Color.RED),
+                new Notification.ProgressStyle.Point(20).setColor(Color.BLUE));
+        final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
+                points,
+                100,
+                true,
+                Color.TRANSPARENT);
+
+        final Bundle bundle = savedModel.toBundle();
+
+        // WHEN
+        final NotificationProgressModel restoredModel =
+                NotificationProgressModel.fromBundle(bundle);
+
+        // THEN
+        assertThat(restoredModel.isIndeterminate()).isFalse();
+        assertThat(restoredModel.getSegments()).isEqualTo(segments);
+        assertThat(restoredModel.getPoints()).isEqualTo(points);
+        assertThat(restoredModel.getProgress()).isEqualTo(100);
+        assertThat(restoredModel.isStyledByProgress()).isTrue();
+        assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.TRANSPARENT);
+        assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.TRANSPARENT);
     }
 }
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 0182588..2d4d458 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
@@ -43,7 +43,6 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.window.DesktopModeFlags;
 import android.window.WindowContainerToken;
@@ -83,6 +82,7 @@
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * Manages the recent task list from the system, caching it as necessary.
@@ -124,6 +124,10 @@
      * Cached list of the visible tasks, sorted from top most to bottom most.
      */
     private final List<RunningTaskInfo> mVisibleTasks = new ArrayList<>();
+    private final Map<Integer, TaskInfo> mVisibleTasksMap = new HashMap<>();
+
+    // Temporary vars used in `generateList()`
+    private final Map<Integer, TaskInfo> mTmpRemaining = new HashMap<>();
 
     /**
      * Creates {@link RecentTasksController}, returns {@code null} if the feature is not
@@ -348,8 +352,11 @@
     public void onVisibleTasksChanged(@NonNull List<? extends RunningTaskInfo> visibleTasks) {
         mVisibleTasks.clear();
         mVisibleTasks.addAll(visibleTasks);
+        mVisibleTasksMap.clear();
+        mVisibleTasksMap.putAll(mVisibleTasks.stream().collect(
+                Collectors.toMap(TaskInfo::getTaskId, task -> task)));
         // Notify with all the info and not just the running task info
-        notifyVisibleTasksChanged(visibleTasks);
+        notifyVisibleTasksChanged(mVisibleTasks);
     }
 
     @VisibleForTesting
@@ -458,7 +465,7 @@
         }
         try {
             // Compute the visible recent tasks in order, and move the task to the top
-            mListener.onVisibleTasksChanged(generateList(visibleTasks)
+            mListener.onVisibleTasksChanged(generateList(visibleTasks, "visibleTasksChanged")
                     .toArray(new GroupedTaskInfo[0]));
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed call onVisibleTasksChanged", e);
@@ -494,40 +501,87 @@
     @VisibleForTesting
     ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
         // Note: the returned task list is ordered from the most-recent to least-recent order
-        return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId));
+        return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId),
+                "getRecentTasks");
     }
 
     /**
-     * Generates a list of GroupedTaskInfos for the given list of tasks.
+     * Returns whether the given task should be excluded from the generated list.
      */
-    private <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks) {
-        // Make a mapping of task id -> task info
-        final SparseArray<TaskInfo> rawMapping = new SparseArray<>();
-        for (int i = 0; i < tasks.size(); i++) {
-            final TaskInfo taskInfo = tasks.get(i);
-            rawMapping.put(taskInfo.taskId, taskInfo);
+    private boolean excludeTaskFromGeneratedList(TaskInfo taskInfo) {
+        if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+            // We don't current send pinned tasks as a part of recent or running tasks
+            return true;
+        }
+        if (isWallpaperTask(taskInfo)) {
+            // Don't add the fullscreen wallpaper task as an entry in grouped tasks
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Generates a list of GroupedTaskInfos for the given raw list of tasks (either recents or
+     * running tasks).
+     *
+     * The general flow is:
+     *  - Collect the desktop tasks
+     *  - Collect the visible tasks (in order), including the desktop tasks if visible
+     *  - Construct the final list with the visible tasks, followed by the subsequent tasks
+     *      - if enableShellTopTaskTracking() is enabled, the visible tasks will be grouped into
+     *        a single mixed task
+     *      - if the desktop tasks are not visible, they will be appended to the end of the list
+     *
+     * TODO(346588978): Generate list in per-display order
+     *
+     * @param tasks The list of tasks ordered from most recent to least recent
+     */
+    @VisibleForTesting
+    <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks,
+            String reason) {
+        if (tasks.isEmpty()) {
+            return new ArrayList<>();
         }
 
-        ArrayList<TaskInfo> freeformTasks = new ArrayList<>();
-        Set<Integer> minimizedFreeformTasks = new HashSet<>();
+        if (enableShellTopTaskTracking()) {
+            ProtoLog.v(WM_SHELL_TASK_OBSERVER, "RecentTasksController.generateList(%s)", reason);
+        }
 
-        int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
+        // Make a mapping of task id -> task info for the remaining tasks to be processed, this
+        // mapping is used to keep track of split tasks that may exist later in the task list that
+        // should be ignored because they've already been grouped
+        mTmpRemaining.clear();
+        mTmpRemaining.putAll(tasks.stream().collect(
+                Collectors.toMap(TaskInfo::getTaskId, task -> task)));
 
-        ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>();
-        // Pull out the pairs as we iterate back in the list
+        // The final grouped tasks
+        ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>(tasks.size());
+        ArrayList<GroupedTaskInfo> visibleGroupedTasks = new ArrayList<>();
+
+        // Phase 1: Extract the desktop and visible fullscreen/split tasks. We make new collections
+        // here as the GroupedTaskInfo can store them without copying
+        ArrayList<TaskInfo> desktopTasks = new ArrayList<>();
+        Set<Integer> minimizedDesktopTasks = new HashSet<>();
+        boolean desktopTasksVisible = false;
         for (int i = 0; i < tasks.size(); i++) {
             final TaskInfo taskInfo = tasks.get(i);
-            if (!rawMapping.contains(taskInfo.taskId)) {
-                // If it's not in the mapping, then it was already paired with another task
+            final int taskId = taskInfo.taskId;
+
+            if (!mTmpRemaining.containsKey(taskInfo.taskId)) {
+                // Skip if we've already processed it
                 continue;
             }
+
+            if (excludeTaskFromGeneratedList(taskInfo)) {
+                // Skip and update the list if we are excluding this task
+                mTmpRemaining.remove(taskId);
+                continue;
+            }
+
+            // Desktop tasks
             if (DesktopModeStatus.canEnterDesktopMode(mContext) &&
-                mDesktopUserRepositories.isPresent()
-                    && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskInfo.taskId)) {
-                // Freeform tasks will be added as a separate entry
-                if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
-                    mostRecentFreeformTaskIndex = groupedTasks.size();
-                }
+                    mDesktopUserRepositories.isPresent()
+                    && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskId)) {
                 // If task has their app bounds set to null which happens after reboot, set the
                 // app bounds to persisted lastFullscreenBounds. Also set the position in parent
                 // to the top left of the bounds.
@@ -538,49 +592,132 @@
                     taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left,
                             taskInfo.lastNonFullscreenBounds.top);
                 }
-                freeformTasks.add(taskInfo);
-                if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskInfo.taskId)) {
-                    minimizedFreeformTasks.add(taskInfo.taskId);
+                desktopTasks.add(taskInfo);
+                if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskId)) {
+                    minimizedDesktopTasks.add(taskId);
                 }
+                desktopTasksVisible |= mVisibleTasksMap.containsKey(taskId);
+                mTmpRemaining.remove(taskId);
                 continue;
             }
 
-            final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
-            if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) {
-                final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
-                rawMapping.remove(pairedTaskId);
-                groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
-                        mTaskSplitBoundsMap.get(pairedTaskId)));
+            if (enableShellTopTaskTracking()) {
+                // Visible tasks
+                if (mVisibleTasksMap.containsKey(taskId)) {
+                    // Split tasks
+                    if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining,
+                            visibleGroupedTasks)) {
+                        continue;
+                    }
+
+                    // Fullscreen tasks
+                    visibleGroupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
+                    mTmpRemaining.remove(taskId);
+                }
             } else {
-                if (isWallpaperTask(taskInfo)) {
-                    // Don't add the wallpaper task as an entry in grouped tasks
+                // Split tasks
+                if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) {
                     continue;
                 }
-                // TODO(346588978): Consolidate multiple visible fullscreen tasks into the same
-                //  grouped task
+
+                // Fullscreen tasks
                 groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
             }
         }
 
-        // Add a special entry for freeform tasks
-        if (!freeformTasks.isEmpty()) {
-            groupedTasks.add(mostRecentFreeformTaskIndex,
-                    GroupedTaskInfo.forFreeformTasks(
-                            freeformTasks,
-                            minimizedFreeformTasks));
-        }
-
         if (enableShellTopTaskTracking()) {
-            // We don't current send pinned tasks as a part of recent or running tasks, so remove
-            // them from the list here
-            groupedTasks.removeIf(
-                    gti -> gti.getTaskInfo1().getWindowingMode() == WINDOWING_MODE_PINNED);
+            // Phase 2: If there were desktop tasks and they are visible, add them to the visible
+            //          list as well (the actual order doesn't matter for Overview)
+            if (!desktopTasks.isEmpty() && desktopTasksVisible) {
+                visibleGroupedTasks.add(
+                        GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+            }
+
+            if (!visibleGroupedTasks.isEmpty()) {
+                // Phase 3: Combine the visible tasks into a single mixed grouped task, only if
+                //          there are > 1 tasks to group, and add them to the final list
+                if (visibleGroupedTasks.size() > 1) {
+                    groupedTasks.add(GroupedTaskInfo.forMixed(visibleGroupedTasks));
+                } else {
+                    groupedTasks.addAll(visibleGroupedTasks);
+                }
+            }
+            dumpGroupedTasks(groupedTasks, "Phase 3");
+
+            // Phase 4: For the remaining non-visible split and fullscreen tasks, add grouped tasks
+            //          in order to the final list
+            for (int i = 0; i < tasks.size(); i++) {
+                final TaskInfo taskInfo = tasks.get(i);
+                if (!mTmpRemaining.containsKey(taskInfo.taskId)) {
+                    // Skip if we've already processed it
+                    continue;
+                }
+
+                // Split tasks
+                if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) {
+                    continue;
+                }
+
+                // Fullscreen tasks
+                groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
+            }
+            dumpGroupedTasks(groupedTasks, "Phase 4");
+
+            // Phase 5: If there were desktop tasks and they are not visible (ie. weren't added
+            //          above), add them to the end of the final list (the actual order doesn't
+            //          matter for Overview)
+            if (!desktopTasks.isEmpty() && !desktopTasksVisible) {
+                groupedTasks.add(
+                        GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+            }
+            dumpGroupedTasks(groupedTasks, "Phase 5");
+        } else {
+            // Add the desktop tasks at the end of the list
+            if (!desktopTasks.isEmpty()) {
+                groupedTasks.add(
+                        GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+            }
         }
 
         return groupedTasks;
     }
 
     /**
+     * Only to be called from `generateList()`. If the given {@param taskInfo} has a paired task,
+     * then a split grouped task with the pair is added to {@param tasksOut}.
+     *
+     * @return whether a split task was extracted and added to the given list
+     */
+    private boolean extractAndAddSplitGroupedTask(@NonNull TaskInfo taskInfo,
+            @NonNull Map<Integer, TaskInfo> remainingTasks,
+            @NonNull ArrayList<GroupedTaskInfo> tasksOut) {
+        final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
+        if (pairedTaskId == INVALID_TASK_ID || !remainingTasks.containsKey(pairedTaskId)) {
+            return false;
+        }
+
+        // Add both this task and its pair to the list, and mark the paired task to be
+        // skipped when it is encountered in the list
+        final TaskInfo pairedTaskInfo = remainingTasks.get(pairedTaskId);
+        remainingTasks.remove(taskInfo.taskId);
+        remainingTasks.remove(pairedTaskId);
+        tasksOut.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
+                mTaskSplitBoundsMap.get(pairedTaskId)));
+        return true;
+    }
+
+    /** Dumps the set of tasks to protolog */
+    private void dumpGroupedTasks(List<GroupedTaskInfo> groupedTasks, String reason) {
+        if (!WM_SHELL_TASK_OBSERVER.isEnabled()) {
+            return;
+        }
+        ProtoLog.v(WM_SHELL_TASK_OBSERVER, "    Tasks (%s):", reason);
+        for (GroupedTaskInfo task : groupedTasks) {
+            ProtoLog.v(WM_SHELL_TASK_OBSERVER, "        %s", task);
+        }
+    }
+
+    /**
      * Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified.
      * NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 93f2e4c..11cd403 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -193,16 +193,18 @@
     override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
 
     override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
-        if (enableShellTopTaskTracking()) {
-            if (pendingCloseTasks.isNotEmpty()) {
-                // Update the visible task list based on the pending close tasks
-                for (change in pendingCloseTasks) {
-                    visibleTasks.removeIf {
-                        it.taskId == change.taskId
-                    }
+        if (!enableShellTopTaskTracking()) {
+            return
+        }
+
+        if (pendingCloseTasks.isNotEmpty()) {
+            // Update the visible task list based on the pending close tasks
+            for (change in pendingCloseTasks) {
+                visibleTasks.removeIf {
+                    it.taskId == change.taskId
                 }
-                updateVisibleTasksList("transition-finished")
             }
+            updateVisibleTasksList("transition-finished")
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 792f5ca..7aa0037 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -284,6 +284,10 @@
     }
 
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+        if (mDisplayController.getDisplay(taskInfo.displayId) == null) {
+            // If DisplayController doesn't have it tracked, it could be a private/managed display.
+            return false;
+        }
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             return true;
         }
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 eb3a698..51b0291 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
@@ -1643,6 +1643,10 @@
     }
 
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+        if (mDisplayController.getDisplay(taskInfo.displayId) == null) {
+            // If DisplayController doesn't have it tracked, it could be a private/managed display.
+            return false;
+        }
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
         if (mSplitScreenController != null
                 && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 28f4ea0..065fa21 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
@@ -28,7 +29,6 @@
 import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -48,9 +48,10 @@
 import static org.mockito.Mockito.when;
 
 import static java.lang.Integer.MAX_VALUE;
+import static java.util.stream.Collectors.joining;
 
-import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
 import android.app.KeyguardManager;
 import android.content.ComponentName;
@@ -247,10 +248,10 @@
 
         ArrayList<GroupedTaskInfo> recentTasks =
                 mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
-        assertGroupedTasksListEquals(recentTasks,
-                t1.taskId, -1,
-                t2.taskId, -1,
-                t3.taskId, -1);
+        assertGroupedTasksListEquals(recentTasks, List.of(
+                List.of(t1.taskId),
+                List.of(t2.taskId),
+                List.of(t3.taskId)));
     }
 
     @Test
@@ -262,7 +263,9 @@
 
         ArrayList<GroupedTaskInfo> recentTasks =
                 mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
-        assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t3.taskId, -1);
+        assertGroupedTasksListEquals(recentTasks, List.of(
+                List.of(t1.taskId),
+                List.of(t3.taskId)));
     }
 
     @Test
@@ -286,11 +289,11 @@
 
         ArrayList<GroupedTaskInfo> recentTasks =
                 mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
-        assertGroupedTasksListEquals(recentTasks,
-                t1.taskId, -1,
-                t2.taskId, t4.taskId,
-                t3.taskId, t5.taskId,
-                t6.taskId, -1);
+        assertGroupedTasksListEquals(recentTasks, List.of(
+                List.of(t1.taskId),
+                List.of(t2.taskId, t4.taskId),
+                List.of(t3.taskId, t5.taskId),
+                List.of(t6.taskId)));
     }
 
     @Test
@@ -320,11 +323,11 @@
                         consumer);
         mMainExecutor.flushAll();
 
-        assertGroupedTasksListEquals(recentTasks[0],
-                t1.taskId, -1,
-                t2.taskId, t4.taskId,
-                t3.taskId, t5.taskId,
-                t6.taskId, -1);
+        assertGroupedTasksListEquals(recentTasks[0], List.of(
+                List.of(t1.taskId),
+                List.of(t2.taskId, t4.taskId),
+                List.of(t3.taskId, t5.taskId),
+                List.of(t6.taskId)));
     }
 
     @Test
@@ -343,9 +346,9 @@
 
         // 2 freeform tasks should be grouped into one, 3 total recents entries
         assertEquals(3, recentTasks.size());
-        GroupedTaskInfo freeformGroup = recentTasks.get(0);
-        GroupedTaskInfo singleGroup1 = recentTasks.get(1);
-        GroupedTaskInfo singleGroup2 = recentTasks.get(2);
+        GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+        GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+        GroupedTaskInfo freeformGroup = recentTasks.get(2);
 
         // Check that groups have expected types
         assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM));
@@ -383,8 +386,8 @@
         // 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries
         assertEquals(3, recentTasks.size());
         GroupedTaskInfo splitGroup = recentTasks.get(0);
-        GroupedTaskInfo freeformGroup = recentTasks.get(1);
-        GroupedTaskInfo singleGroup = recentTasks.get(2);
+        GroupedTaskInfo singleGroup = recentTasks.get(1);
+        GroupedTaskInfo freeformGroup = recentTasks.get(2);
 
         // Check that groups have expected types
         assertTrue(splitGroup.isBaseType(TYPE_SPLIT));
@@ -454,9 +457,9 @@
 
         // 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries
         assertEquals(3, recentTasks.size());
-        GroupedTaskInfo freeformGroup = recentTasks.get(0);
-        GroupedTaskInfo singleGroup1 = recentTasks.get(1);
-        GroupedTaskInfo singleGroup2 = recentTasks.get(2);
+        GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+        GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+        GroupedTaskInfo freeformGroup = recentTasks.get(2);
 
         // Check that groups have expected types
         assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM));
@@ -523,7 +526,7 @@
 
         // Remove one of the tasks and ensure the pair is removed
         SurfaceControl mockLeash = mock(SurfaceControl.class);
-        ActivityManager.RunningTaskInfo rt2 = makeRunningTaskInfo(2);
+        RunningTaskInfo rt2 = makeRunningTaskInfo(2);
         mShellTaskOrganizer.onTaskAppeared(rt2, mockLeash);
         mShellTaskOrganizer.onTaskVanished(rt2);
 
@@ -537,13 +540,13 @@
 
         // Remove one of the tasks and ensure the pair is removed
         SurfaceControl mockLeash = mock(SurfaceControl.class);
-        ActivityManager.RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
+        RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
         rt2Fullscreen.configuration.windowConfiguration.setWindowingMode(
                 WINDOWING_MODE_FULLSCREEN);
         mShellTaskOrganizer.onTaskAppeared(rt2Fullscreen, mockLeash);
 
         // Change the windowing mode and ensure the recent tasks change is notified
-        ActivityManager.RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
+        RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
         rt2MultiWIndow.configuration.windowConfiguration.setWindowingMode(
                 WINDOWING_MODE_MULTI_WINDOW);
         mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow);
@@ -557,7 +560,7 @@
     public void onTaskAdded_desktopModeRunningAppsEnabled_triggersOnRunningTaskAppeared()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskAdded(taskInfo);
 
@@ -570,7 +573,7 @@
     public void onTaskAdded_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskAppeared()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskAdded(taskInfo);
 
@@ -583,7 +586,7 @@
     public void taskWindowingModeChanged_desktopRunningAppsEnabled_triggersOnRunningTaskChanged()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
 
@@ -597,7 +600,7 @@
             taskWindowingModeChanged_desktopRunningAppsDisabled_doesNotTriggerOnRunningTaskChanged()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
 
@@ -610,7 +613,7 @@
     public void onTaskRemoved_desktopModeRunningAppsEnabled_triggersOnRunningTaskVanished()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskRemoved(taskInfo);
 
@@ -623,7 +626,7 @@
     public void onTaskRemoved_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskVanished()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskRemoved(taskInfo);
 
@@ -632,10 +635,11 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
     public void onTaskMovedToFront_TaskStackObserverEnabled_triggersOnTaskMovedToFront()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
 
@@ -648,7 +652,7 @@
     public void onTaskMovedToFront_TaskStackObserverEnabled_doesNotTriggersOnTaskMovedToFront()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskMovedToFront(taskInfo);
 
@@ -700,7 +704,7 @@
     @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
     public void shellTopTaskTracker_onTaskRemoved_expectNoRecentsChanged() throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
         mRecentTasksControllerReal.onTaskRemoved(taskInfo);
         verify(mRecentTasksListener, never()).onRecentTasksChanged();
     }
@@ -710,22 +714,105 @@
     @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
     public void shellTopTaskTracker_onVisibleTasksChanged() throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
         mRecentTasksControllerReal.onVisibleTasksChanged(List.of(taskInfo));
         verify(mRecentTasksListener, never()).onVisibleTasksChanged(any());
     }
 
+    @Test
+    public void generateList_emptyTaskList_expectNoGroupedTasks() throws Exception {
+        assertTrue(mRecentTasksControllerReal.generateList(List.of(), "test").isEmpty());
+    }
+
+    @Test
+    public void generateList_excludePipTask() throws Exception {
+        RunningTaskInfo task1 = makeRunningTaskInfo(1);
+        RunningTaskInfo pipTask = makeRunningTaskInfo(2);
+        pipTask.configuration.windowConfiguration.setWindowingMode(
+                WINDOWING_MODE_PINNED);
+
+        ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+                List.of(task1, pipTask),
+                "test");
+
+        assertGroupedTasksListEquals(groupedTasks, List.of(List.of(task1.taskId)));
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+    public void generateList_fullscreen_noVisibleTasks_expectNoGrouping() throws Exception {
+        RunningTaskInfo task1 = makeRunningTaskInfo(1);
+        RunningTaskInfo task2 = makeRunningTaskInfo(2);
+        RunningTaskInfo task3 = makeRunningTaskInfo(3);
+        // Reset visible tasks list
+        mRecentTasksControllerReal.onVisibleTasksChanged(List.of());
+
+        // Generate a list with a number of fullscreen tasks
+        ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+                List.of(task1, task2, task3),
+                "test");
+
+        assertGroupedTasksListEquals(groupedTasks, List.of(
+                List.of(task1.taskId),
+                List.of(task2.taskId),
+                List.of(task3.taskId)));
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+    public void generateList_fullscreen_singleVisibleTask_expectNoGrouping() throws Exception {
+        RunningTaskInfo task1 = makeRunningTaskInfo(1);
+        RunningTaskInfo task2 = makeRunningTaskInfo(2);
+        RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+        // Reset visible tasks list
+        mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1));
+
+        // Generate a list with a number of fullscreen tasks
+        ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+                List.of(task1, task2, task3),
+                "test");
+
+        assertGroupedTasksListEquals(groupedTasks, List.of(
+                List.of(task1.taskId),
+                List.of(task2.taskId),
+                List.of(task3.taskId)));
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+    public void generateList_fullscreen_multipleVisibleTasks_expectGrouping() throws Exception {
+        RunningTaskInfo task1 = makeRunningTaskInfo(1);
+        RunningTaskInfo task2 = makeRunningTaskInfo(2);
+        RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+        // Reset visible tasks list
+        mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1, task2));
+
+        // Generate a list with a number of fullscreen tasks
+        ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+                List.of(task1, task2, task3),
+                "test");
+
+        assertGroupedTasksListEquals(groupedTasks, List.of(
+                List.of(task1.taskId),
+                List.of(task2.taskId),
+                List.of(task3.taskId)));
+    }
+
     /**
      * Helper to create a task with a given task id.
      */
     private RecentTaskInfo makeTaskInfo(int taskId) {
         RecentTaskInfo info = new RecentTaskInfo();
         info.taskId = taskId;
-
+        info.realActivity = new ComponentName("testPackage", "testClass");
         Intent intent = new Intent();
         intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
         info.baseIntent = intent;
-
         info.lastNonFullscreenBounds = new Rect();
         return info;
     }
@@ -742,10 +829,14 @@
     /**
      * Helper to create a running task with a given task id.
      */
-    private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) {
-        ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
+    private RunningTaskInfo makeRunningTaskInfo(int taskId) {
+        RunningTaskInfo info = new RunningTaskInfo();
         info.taskId = taskId;
         info.realActivity = new ComponentName("testPackage", "testClass");
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
+        info.baseIntent = intent;
+        info.lastNonFullscreenBounds = new Rect();
         return info;
     }
 
@@ -759,37 +850,42 @@
 
     /**
      * Asserts that the recent tasks matches the given task ids.
+     * TODO(346588978): Separate out specific split verification during the iteration below
      *
-     * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
-     *                        the grouped task list
+     * @param expectedTaskIds a list of expected grouped task ids (itself a list of ints)
      */
-    private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks,
-            int... expectedTaskIds) {
-        int[] flattenedTaskIds = new int[recentTasks.size() * 2];
-        for (int i = 0; i < recentTasks.size(); i++) {
-            GroupedTaskInfo pair = recentTasks.get(i);
-            int taskId1 = pair.getTaskInfo1().taskId;
-            flattenedTaskIds[2 * i] = taskId1;
-            flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null
-                    ? pair.getTaskInfo2().taskId
-                    : -1;
+    private void assertGroupedTasksListEquals(List<GroupedTaskInfo> groupedTasks,
+            List<List<Integer>> expectedTaskIds) {
+        List<List<Integer>> foundTaskIds = new ArrayList<>();
+        for (int i = 0; i < groupedTasks.size(); i++) {
+            GroupedTaskInfo groupedTask = groupedTasks.get(i);
+            List<Integer> groupedTaskIds = groupedTask.getTaskInfoList().stream()
+                    .map(taskInfo -> taskInfo.taskId)
+                    .toList();
+            foundTaskIds.add(groupedTaskIds);
 
-            if (pair.getTaskInfo2() != null) {
-                assertNotNull(pair.getSplitBounds());
-                int leftTopTaskId = pair.getSplitBounds().leftTopTaskId;
-                int bottomRightTaskId = pair.getSplitBounds().rightBottomTaskId;
+            if (groupedTask.isBaseType(TYPE_SPLIT)) {
+                assertNotNull(groupedTask.getSplitBounds());
+                int leftTopTaskId = groupedTask.getSplitBounds().leftTopTaskId;
+                int bottomRightTaskId = groupedTask.getSplitBounds().rightBottomTaskId;
                 // Unclear if pairs are ordered by split position, most likely not.
-                assertTrue(leftTopTaskId == taskId1
-                        || leftTopTaskId == pair.getTaskInfo2().taskId);
-                assertTrue(bottomRightTaskId == taskId1
-                        || bottomRightTaskId == pair.getTaskInfo2().taskId);
-            } else {
-                assertNull(pair.getSplitBounds());
+                assertTrue(leftTopTaskId == groupedTaskIds.getFirst()
+                        || leftTopTaskId == groupedTaskIds.getLast());
+                assertTrue(bottomRightTaskId == groupedTaskIds.getFirst()
+                        || bottomRightTaskId == groupedTaskIds.getLast());
             }
         }
-        assertArrayEquals("Expected: " + Arrays.toString(expectedTaskIds)
-                        + " Received: " + Arrays.toString(flattenedTaskIds),
-                flattenedTaskIds,
-                expectedTaskIds);
+        List<Integer> flattenedExpectedTaskIds = expectedTaskIds.stream()
+                .flatMap(List::stream)
+                .toList();
+        List<Integer> flattenedFoundTaskIds = foundTaskIds.stream()
+                .flatMap(List::stream)
+                .toList();
+        assertEquals("Expected: "
+                        + flattenedExpectedTaskIds.stream().map(String::valueOf).collect(joining())
+                        + " Received: "
+                        + flattenedFoundTaskIds.stream().map(String::valueOf).collect(joining()),
+                flattenedExpectedTaskIds,
+                flattenedFoundTaskIds);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index b44af47..908bc99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -31,6 +31,7 @@
 import android.testing.TestableContext
 import android.util.SparseArray
 import android.view.Choreographer
+import android.view.Display
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.IWindowManager
 import android.view.InputChannel
@@ -157,6 +158,7 @@
     protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>()
     protected val motionEvent = mock<MotionEvent>()
     val displayLayout = mock<DisplayLayout>()
+    val display = mock<Display>()
     protected lateinit var spyContext: TestableContext
     private lateinit var desktopModeEventLogger: DesktopModeEventLogger
 
@@ -183,6 +185,7 @@
         desktopModeEventLogger = mock<DesktopModeEventLogger>()
         whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
         whenever(mockDisplayController.getDisplayContext(any())).thenReturn(spyContext)
+        whenever(mockDisplayController.getDisplay(any())).thenReturn(display)
         whenever(mockDesktopUserRepositories.getProfile(anyInt()))
             .thenReturn(mockDesktopRepository)
         desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index ce66a36..c0d2449 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -40,6 +40,7 @@
 import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
 import com.android.settingslib.graph.proto.PreferenceScreenProto
 import com.android.settingslib.graph.proto.TextProto
+import com.android.settingslib.metadata.IntRangeValuePreference
 import com.android.settingslib.metadata.PersistentPreference
 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
 import com.android.settingslib.metadata.PreferenceHierarchy
@@ -50,7 +51,6 @@
 import com.android.settingslib.metadata.PreferenceScreenRegistry
 import com.android.settingslib.metadata.PreferenceSummaryProvider
 import com.android.settingslib.metadata.PreferenceTitleProvider
-import com.android.settingslib.metadata.RangeValue
 import com.android.settingslib.metadata.ReadWritePermit
 import com.android.settingslib.preference.PreferenceScreenFactory
 import com.android.settingslib.preference.PreferenceScreenProvider
@@ -407,22 +407,20 @@
         ) {
             val storage = metadata.storage(context)
             value = preferenceValueProto {
-                when (metadata) {
-                    is RangeValue -> storage.getInt(metadata.key)?.let { intValue = it }
-                    else -> {}
-                }
                 when (metadata.valueType) {
+                    Int::class.javaObjectType -> storage.getInt(metadata.key)?.let { intValue = it }
                     Boolean::class.javaObjectType ->
                         storage.getBoolean(metadata.key)?.let { booleanValue = it }
                     Float::class.javaObjectType ->
                         storage.getFloat(metadata.key)?.let { floatValue = it }
+                    else -> {}
                 }
             }
         }
         if (flags.includeValueDescriptor()) {
             valueDescriptor = preferenceValueDescriptorProto {
                 when (metadata) {
-                    is RangeValue -> rangeValue = rangeValueProto {
+                    is IntRangeValuePreference -> rangeValue = rangeValueProto {
                             min = metadata.getMinValue(context)
                             max = metadata.getMaxValue(context)
                             step = metadata.getIncrementStep(context)
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index 4719064..728055c 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -26,12 +26,12 @@
 import com.android.settingslib.ipc.ApiPermissionChecker
 import com.android.settingslib.ipc.IntMessageCodec
 import com.android.settingslib.ipc.MessageCodec
+import com.android.settingslib.metadata.IntRangeValuePreference
 import com.android.settingslib.metadata.PersistentPreference
 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
 import com.android.settingslib.metadata.PreferenceMetadata
 import com.android.settingslib.metadata.PreferenceRestrictionProvider
 import com.android.settingslib.metadata.PreferenceScreenRegistry
-import com.android.settingslib.metadata.RangeValue
 import com.android.settingslib.metadata.ReadWritePermit
 import com.android.settingslib.metadata.SensitivityLevel.Companion.HIGH_SENSITIVITY
 import com.android.settingslib.metadata.SensitivityLevel.Companion.UNKNOWN_SENSITIVITY
@@ -157,7 +157,10 @@
                 val intValue = value.intValue
                 val resultCode = metadata.checkWritePermit(intValue)
                 if (resultCode != PreferenceSetterResult.OK) return resultCode
-                if (metadata is RangeValue && !metadata.isValidValue(application, intValue)) {
+                if (
+                    metadata is IntRangeValuePreference &&
+                        !metadata.isValidValue(application, intValue)
+                ) {
                     return PreferenceSetterResult.INVALID_REQUEST
                 }
                 storage.setInt(key, intValue)
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
index be705b5..63f1050 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -75,7 +75,7 @@
     }
 }
 
-/** Preference interface that has a value persisted in datastore. */
+/** Preference metadata that has a value persisted in datastore. */
 interface PersistentPreference<T> : PreferenceMetadata {
 
     /**
@@ -204,18 +204,3 @@
     override fun getValue(context: Context, index: Int): Int =
         context.resources.getIntArray(values)[index]
 }
-
-/** Value is between a range. */
-interface RangeValue : ValueDescriptor {
-    /** The lower bound (inclusive) of the range. */
-    fun getMinValue(context: Context): Int
-
-    /** The upper bound (inclusive) of the range. */
-    fun getMaxValue(context: Context): Int
-
-    /** The increment step within the range. 0 means unset, which implies step size is 1. */
-    fun getIncrementStep(context: Context) = 0
-
-    override fun isValidValue(context: Context, index: Int) =
-        index in getMinValue(context)..getMaxValue(context)
-}
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
index fecf3e5..edd45d3 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.metadata
 
+import android.content.Context
 import androidx.annotation.StringRes
 
 /** A persistent preference that has a boolean value. */
@@ -30,6 +31,24 @@
         get() = Float::class.javaObjectType
 }
 
+/** A persistent preference that has a int value between a range. */
+interface IntRangeValuePreference : PersistentPreference<Int>, ValueDescriptor {
+    override val valueType: Class<Int>
+        get() = Int::class.javaObjectType
+
+    /** The lower bound (inclusive) of the range. */
+    fun getMinValue(context: Context): Int
+
+    /** The upper bound (inclusive) of the range. */
+    fun getMaxValue(context: Context): Int
+
+    /** The increment step within the range. 0 means unset, which implies step size is 1. */
+    fun getIncrementStep(context: Context) = 0
+
+    override fun isValidValue(context: Context, index: Int) =
+        index in getMinValue(context)..getMaxValue(context)
+}
+
 /** A preference that provides a two-state toggleable option. */
 open class SwitchPreference
 @JvmOverloads
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index e4bc7b4..04df308 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,6 +3,7 @@
 chiujason@google.com
 cipson@google.com
 dsandler@android.com
+dswliu@google.com
 edgarwang@google.com
 evanlaird@google.com
 jiannan@google.com
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index c61c6a5..59141c9 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -25,10 +25,10 @@
 import androidx.preference.SeekBarPreference
 import com.android.settingslib.metadata.DiscreteIntValue
 import com.android.settingslib.metadata.DiscreteValue
+import com.android.settingslib.metadata.IntRangeValuePreference
 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
 import com.android.settingslib.metadata.PreferenceMetadata
 import com.android.settingslib.metadata.PreferenceScreenMetadata
-import com.android.settingslib.metadata.RangeValue
 
 /** Binding of preference widget and preference metadata. */
 interface PreferenceBinding {
@@ -101,7 +101,7 @@
                 } else {
                     preference.setEntryValues(values)
                 }
-            } else if (preference is SeekBarPreference && this is RangeValue) {
+            } else if (preference is SeekBarPreference && this is IntRangeValuePreference) {
                 preference.min = getMinValue(context)
                 preference.max = getMaxValue(context)
                 preference.seekBarIncrement = getIncrementStep(context)
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
rename to packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
index b5f22b7..ec67d06 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
@@ -104,6 +104,11 @@
         <item name="android:layout_width">match_parent</item>
     </style>
 
+    <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
+        parent="@style/TextAppearance.SettingsLib.LabelLarge">
+        <item name="android:textColor">?android:attr/colorAccent</item>
+    </style>
+
     <style name="SettingslibTextButtonStyle.Expressive"
         parent="@style/Widget.Material3Expressive.Button.TextButton.Icon">
         <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
index 1a08568..3af88c4 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
@@ -70,11 +70,6 @@
         <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
     </style>
 
-    <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
-           parent="@style/TextAppearance.SettingsLib.LabelLarge">
-        <item name="android:textColor">?android:attr/colorAccent</item>
-    </style>
-
     <style name="SettingsLibStatusBannerCardStyle">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
deleted file mode 100644
index 195d45f..0000000
--- a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2022 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingBottom="16dp"
-    android:paddingTop="8dp"
-    android:background="?android:attr/selectableItemBackground"
-    android:clipToPadding="false">
-
-    <TextView
-        android:id="@android:id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="start"
-        android:textAlignment="viewStart"
-        android:clickable="false"
-        android:longClickable="false"
-        android:maxLines="10"
-        android:hyphenationFrequency="normalFast"
-        android:lineBreakWordStyle="phrase"
-        android:textAppearance="@style/TextAppearance.TopIntroText"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
similarity index 95%
rename from packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml
rename to packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
index fb13ef7..834814c 100644
--- a/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2024 The Android Open Source Project
+  Copyright (C) 2025 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.
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
deleted file mode 100644
index bee6bc7..0000000
--- a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  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.
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingBottom="16dp"
-    android:paddingTop="8dp"
-    android:background="?android:attr/selectableItemBackground"
-    android:clipToPadding="false">
-
-    <TextView
-        android:id="@android:id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="start"
-        android:textAlignment="viewStart"
-        android:clickable="false"
-        android:longClickable="false"
-        android:maxLines="10"
-        android:textAppearance="@style/TextAppearance.TopIntroText"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
index 4428480..08ba836 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -17,20 +17,20 @@
 package com.android.settingslib.widget
 
 import android.content.Context
-import android.os.Build
 import android.text.TextUtils
 import android.util.AttributeSet
 import android.view.View
-import androidx.annotation.RequiresApi
 import androidx.preference.Preference
 import androidx.preference.PreferenceViewHolder
 import com.android.settingslib.widget.preference.topintro.R
 
-open class TopIntroPreference @JvmOverloads constructor(
+open class TopIntroPreference
+@JvmOverloads
+constructor(
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
-    defStyleRes: Int = 0
+    defStyleRes: Int = 0,
 ) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
 
     private var isCollapsable: Boolean = false
@@ -40,25 +40,18 @@
     private var learnMoreText: CharSequence? = null
 
     init {
-        if (SettingsThemeHelper.isExpressiveTheme(context)) {
-            layoutResource = R.layout.settingslib_expressive_top_intro
-            initAttributes(context, attrs, defStyleAttr)
-        } else {
-            layoutResource = R.layout.top_intro_preference
-        }
+        layoutResource = R.layout.settingslib_expressive_top_intro
+        initAttributes(context, attrs, defStyleAttr)
+
         isSelectable = false
     }
 
     private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
-        context.obtainStyledAttributes(
-            attrs,
-            COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0
-        ).apply {
+        context.obtainStyledAttributes(attrs, COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0).apply {
             isCollapsable = getBoolean(IS_COLLAPSABLE, false)
-            minLines = getInt(
-                MIN_LINES,
-                if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
-            ).coerceIn(1, DEFAULT_MAX_LINES)
+            minLines =
+                getInt(MIN_LINES, if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES)
+                    .coerceIn(1, DEFAULT_MAX_LINES)
             recycle()
         }
     }
@@ -68,10 +61,6 @@
         holder.isDividerAllowedAbove = false
         holder.isDividerAllowedBelow = false
 
-        if (!SettingsThemeHelper.isExpressiveTheme(context)) {
-            return
-        }
-
         (holder.findViewById(R.id.collapsable_text_view) as? CollapsableTextView)?.apply {
             setCollapsable(isCollapsable)
             setMinLines(minLines)
@@ -89,9 +78,9 @@
 
     /**
      * Sets whether the text view is collapsable.
+     *
      * @param collapsable True if the text view should be collapsable, false otherwise.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     fun setCollapsable(collapsable: Boolean) {
         isCollapsable = collapsable
         notifyChanged()
@@ -99,9 +88,9 @@
 
     /**
      * Sets the minimum number of lines to display when collapsed.
+     *
      * @param lines The minimum number of lines.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     fun setMinLines(lines: Int) {
         minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
         notifyChanged()
@@ -109,9 +98,9 @@
 
     /**
      * Sets the action when clicking on the hyperlink in the text.
+     *
      * @param listener The click listener for hyperlink.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     fun setHyperlinkListener(listener: View.OnClickListener) {
         if (hyperlinkListener != listener) {
             hyperlinkListener = listener
@@ -121,9 +110,9 @@
 
     /**
      * Sets the action when clicking on the learn more view.
+     *
      * @param listener The click listener for learn more.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     fun setLearnMoreAction(listener: View.OnClickListener) {
         if (learnMoreListener != listener) {
             learnMoreListener = listener
@@ -133,9 +122,9 @@
 
     /**
      * Sets the text of learn more view.
+     *
      * @param text The text of learn more.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     fun setLearnMoreText(text: CharSequence) {
         if (!TextUtils.equals(learnMoreText, text)) {
             learnMoreText = text
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 640829e..d936a5c6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -287,5 +287,9 @@
         Settings.Secure.ADVANCED_PROTECTION_MODE,
         Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS,
         Settings.Secure.EM_VALUE,
+        Settings.Secure.FACE_APP_ENABLED,
+        Settings.Secure.FACE_KEYGUARD_ENABLED,
+        Settings.Secure.FINGERPRINT_APP_ENABLED,
+        Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 1f56f10..cf0447f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -117,6 +117,7 @@
                 Settings.System.TOUCHPAD_TAP_TO_CLICK,
                 Settings.System.TOUCHPAD_TAP_DRAGGING,
                 Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
+                Settings.System.TOUCHPAD_ACCELERATION_ENABLED,
                 Settings.System.CAMERA_FLASH_NOTIFICATION,
                 Settings.System.SCREEN_FLASH_NOTIFICATION,
                 Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 9fd0cc8..919c3c4 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -456,5 +456,9 @@
                 new InclusiveIntegerRangeValidator(0, 1));
         VALIDATORS.put(Secure.ADVANCED_PROTECTION_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.FACE_APP_ENABLED,  BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.FACE_KEYGUARD_ENABLED,  BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.FINGERPRINT_APP_ENABLED,  BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.FINGERPRINT_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 4d98a11..4f649ed 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -234,6 +234,7 @@
         VALIDATORS.put(System.TOUCHPAD_TAP_DRAGGING, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.TOUCHPAD_RIGHT_CLICK_ZONE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.TOUCHPAD_SYSTEM_GESTURES, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.TOUCHPAD_ACCELERATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.LOCK_TO_APP_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 System.EGG_MODE,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 0224b29..0a7d880 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -521,6 +521,7 @@
         "androidx.compose.animation_animation-graphics",
         "androidx.lifecycle_lifecycle-viewmodel-compose",
         "kairos",
+        "aconfig_settings_flags_lib",
     ],
     libs: [
         "keepanno-annotations",
@@ -706,6 +707,7 @@
         "androidx.lifecycle_lifecycle-viewmodel-compose",
         "TraceurCommon",
         "Traceur-res",
+        "aconfig_settings_flags_lib",
     ],
 }
 
@@ -714,6 +716,9 @@
     use_resource_processor: true,
     manifest: "tests/AndroidManifest-base.xml",
     resource_dirs: [],
+
+    kotlin_lang_version: "1.9",
+
     additional_manifests: ["tests/AndroidManifest.xml"],
     srcs: [
         "tests/src/**/*.kt",
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
index d992403..fc01c78 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
@@ -18,10 +18,10 @@
 
 import androidx.annotation.VisibleForTesting
 import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.foundation.OverscrollEffect
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
@@ -41,7 +41,7 @@
 class OffsetOverscrollEffect(
     orientation: Orientation,
     animationScope: CoroutineScope,
-    animationSpec: AnimationSpec<Float> = DefaultAnimationSpec,
+    animationSpec: AnimationSpec<Float>,
 ) : BaseContentOverscrollEffect(orientation, animationScope, animationSpec) {
     private var _node: DelegatableNode = newNode()
     override val node: DelegatableNode
@@ -71,13 +71,6 @@
     companion object {
         private val MaxDistance = 400.dp
 
-        internal val DefaultAnimationSpec =
-            spring(
-                stiffness = Spring.StiffnessLow,
-                dampingRatio = Spring.DampingRatioLowBouncy,
-                visibilityThreshold = 0.5f,
-            )
-
         @VisibleForTesting
         fun computeOffset(density: Density, overscrollDistance: Float): Int {
             val maxDistancePx = with(density) { MaxDistance.toPx() }
@@ -87,10 +80,11 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun rememberOffsetOverscrollEffect(
     orientation: Orientation,
-    animationSpec: AnimationSpec<Float> = OffsetOverscrollEffect.DefaultAnimationSpec,
+    animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.defaultSpatialSpec(),
 ): OffsetOverscrollEffect {
     val animationScope = rememberCoroutineScope()
     return remember(orientation, animationScope, animationSpec) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index d43b596..456edaf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -89,9 +89,9 @@
 import com.android.compose.animation.Easings
 import com.android.compose.animation.scene.ContentScope
 import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.transitions
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
@@ -494,7 +494,7 @@
     val currentSceneKey =
         if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
 
-    val state = remember { MutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions) }
+    val state = rememberMutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions)
 
     // Update state whenever currentSceneKey has changed.
     LaunchedEffect(state, currentSceneKey) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 9b5ff7f..824f5a2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -43,6 +43,7 @@
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.observableTransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.transitions
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
@@ -165,13 +166,13 @@
         viewModel.communalBackground.collectAsStateWithLifecycle(
             initialValue = CommunalBackgroundType.ANIMATED
         )
-    val state: MutableSceneTransitionLayoutState = remember {
-        MutableSceneTransitionLayoutState(
+    val state: MutableSceneTransitionLayoutState =
+        rememberMutableSceneTransitionLayoutState(
             initialScene = currentSceneKey,
             canChangeScene = { _ -> viewModel.canChangeScene() },
             transitions = if (viewModel.v2FlagEnabled()) sceneTransitionsV2 else sceneTransitions,
         )
-    }
+
     val isUiBlurred by viewModel.isUiBlurred.collectAsStateWithLifecycle()
 
     val detector = remember { CommunalSwipeDetector() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 79cf24b..410499a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -27,7 +27,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
@@ -36,7 +35,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ContentScope
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
@@ -82,9 +81,11 @@
                     WeatherClockScenes.splitShadeLargeClockScene
             }
 
-        val state = remember {
-            MutableSceneTransitionLayoutState(currentScene, ClockTransition.defaultClockTransitions)
-        }
+        val state =
+            rememberMutableSceneTransitionLayoutState(
+                currentScene,
+                ClockTransition.defaultClockTransitions,
+            )
 
         // Update state whenever currentSceneKey has changed.
         LaunchedEffect(state, currentScene) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index a6a6362..0ca7a6a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -36,7 +36,6 @@
 import androidx.compose.ui.platform.LocalView
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ContentKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
@@ -44,6 +43,7 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.observableTransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
 import com.android.systemui.lifecycle.rememberActivated
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.qs.ui.composable.QuickSettingsTheme
@@ -94,29 +94,27 @@
     val sceneJankMonitor =
         rememberActivated(traceName = "sceneJankMonitor") { sceneJankMonitorFactory.create() }
 
-    val state: MutableSceneTransitionLayoutState =
-        remember(view, sceneJankMonitor) {
-            MutableSceneTransitionLayoutState(
-                initialScene = initialSceneKey,
-                canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
-                transitions = sceneTransitions,
-                onTransitionStart = { transition ->
-                    sceneJankMonitor.onTransitionStart(
-                        view = view,
-                        from = transition.fromContent,
-                        to = transition.toContent,
-                        cuj = transition.cuj,
-                    )
-                },
-                onTransitionEnd = { transition ->
-                    sceneJankMonitor.onTransitionEnd(
-                        from = transition.fromContent,
-                        to = transition.toContent,
-                        cuj = transition.cuj,
-                    )
-                },
-            )
-        }
+    val state =
+        rememberMutableSceneTransitionLayoutState(
+            initialScene = initialSceneKey,
+            canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
+            transitions = sceneTransitions,
+            onTransitionStart = { transition ->
+                sceneJankMonitor.onTransitionStart(
+                    view = view,
+                    from = transition.fromContent,
+                    to = transition.toContent,
+                    cuj = transition.cuj,
+                )
+            },
+            onTransitionEnd = { transition ->
+                sceneJankMonitor.onTransitionEnd(
+                    from = transition.fromContent,
+                    to = transition.toContent,
+                    cuj = transition.cuj,
+                )
+            },
+        )
 
     DisposableEffect(state) {
         val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
@@ -179,6 +177,12 @@
                 }
             }
     ) {
+        SceneRevealScrim(
+            viewModel = viewModel.lightRevealScrim,
+            wallpaperViewModel = viewModel.wallpaperViewModel,
+            modifier = Modifier.fillMaxSize(),
+        )
+
         SceneTransitionLayout(
             state = state,
             modifier = modifier.fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index aa8b4ae..d7545cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -1,6 +1,5 @@
 package com.android.systemui.scene.ui.composable
 
-import androidx.compose.animation.core.spring
 import com.android.compose.animation.scene.TransitionKey
 import com.android.compose.animation.scene.transitions
 import com.android.internal.jank.Cuj
@@ -48,9 +47,6 @@
 val SceneContainerTransitions = transitions {
     interruptionHandler = SceneContainerInterruptionHandler
 
-    defaultMotionSpatialSpec =
-        spring(stiffness = 300f, dampingRatio = 0.8f, visibilityThreshold = 0.5f)
-
     // Scene transitions
 
     from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt
new file mode 100644
index 0000000..a1f89fcb
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 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.scene.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
+
+@Composable
+fun SceneRevealScrim(
+    viewModel: LightRevealScrimViewModel,
+    wallpaperViewModel: WallpaperViewModel,
+    modifier: Modifier = Modifier,
+) {
+    AndroidView(
+        factory = { context ->
+            LightRevealScrim(context).apply {
+                LightRevealScrimViewBinder.bind(
+                    revealScrim = this,
+                    viewModel = viewModel,
+                    wallpaperViewModel = wallpaperViewModel,
+                )
+            }
+        },
+        modifier = modifier,
+    )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index e30e7d3..b53c4e238 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
@@ -30,11 +28,6 @@
 
 fun TransitionBuilder.goneToSplitShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
     distance = UserActionDistance { fromContent, _, _ ->
         val fromContentSize = checkNotNull(fromContent.targetSize())
         fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
index 1a243ca..6c8885e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
@@ -29,11 +27,6 @@
 
 fun TransitionBuilder.lockscreenToSplitShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
     distance = UserActionDistance { fromContent, _, _ ->
         val fromContentSize = checkNotNull(fromContent.targetSize())
         fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
index a9af95b..9a7f99f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
@@ -16,22 +16,14 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.shade.ui.composable.Shade
 import kotlin.time.Duration.Companion.milliseconds
 
 fun TransitionBuilder.notificationsShadeToQuickSettingsShadeTransition(
     durationScale: Double = 1.0
 ) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
 }
 
 private val DefaultDuration = 300.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index ddea585..019639d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
@@ -26,16 +24,10 @@
 import com.android.systemui.notifications.ui.composable.NotificationsShade
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.composable.Shade
 import kotlin.time.Duration.Companion.milliseconds
 
 fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
     // Ensure the clock isn't clipped by the shade outline during the transition from lockscreen.
     sharedElement(
         ClockElementKeys.smallClockElementKey,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index e477a41..faccf14 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -16,23 +16,15 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
 import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.composable.Shade
 import kotlin.time.Duration.Companion.milliseconds
 
 fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
     distance = UserActionDistance { fromContent, _, _ ->
         val fromContentSize = checkNotNull(fromContent.targetSize())
         fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index 4db4934..2ed296b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
@@ -32,11 +30,6 @@
 
 fun TransitionBuilder.toShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
     distance = UserActionDistance { _, _, _ ->
         Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index d876606..b04d89d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -19,6 +19,7 @@
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.SpringSpec
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import com.android.compose.animation.scene.content.state.TransitionState
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
@@ -32,7 +33,10 @@
 ): Job {
     oneOffAnimation.onRun = {
         // Animate the progress to its target value.
-        val animationSpec = transition.transformationSpec.progressSpec
+        @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+        val animationSpec =
+            transition.transformationSpec.progressSpec
+                ?: layoutState.motionScheme.defaultSpatialSpec()
         val visibilityThreshold =
             (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
         val replacedTransition = transition.replacedTransition
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 8153586..5b275a5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -14,14 +14,22 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
+
 package com.android.compose.animation.scene
 
 import android.util.Log
 import androidx.annotation.VisibleForTesting
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MotionScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastAny
@@ -226,6 +234,7 @@
  */
 fun MutableSceneTransitionLayoutState(
     initialScene: SceneKey,
+    motionScheme: MotionScheme,
     transitions: SceneTransitions = SceneTransitions.Empty,
     initialOverlays: Set<OverlayKey> = emptySet(),
     canChangeScene: (SceneKey) -> Boolean = { true },
@@ -237,6 +246,7 @@
 ): MutableSceneTransitionLayoutState {
     return MutableSceneTransitionLayoutStateImpl(
         initialScene,
+        motionScheme,
         transitions,
         initialOverlays,
         canChangeScene,
@@ -248,19 +258,62 @@
     )
 }
 
+@Composable
+fun rememberMutableSceneTransitionLayoutState(
+    initialScene: SceneKey,
+    transitions: SceneTransitions = SceneTransitions.Empty,
+    initialOverlays: Set<OverlayKey> = emptySet(),
+    canChangeScene: (SceneKey) -> Boolean = { true },
+    canShowOverlay: (OverlayKey) -> Boolean = { true },
+    canHideOverlay: (OverlayKey) -> Boolean = { true },
+    canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
+    onTransitionStart: (TransitionState.Transition) -> Unit = {},
+    onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+): MutableSceneTransitionLayoutState {
+    val motionScheme = MaterialTheme.motionScheme
+    val layoutState = remember {
+        MutableSceneTransitionLayoutStateImpl(
+            initialScene = initialScene,
+            motionScheme = motionScheme,
+            transitions = transitions,
+            initialOverlays = initialOverlays,
+            canChangeScene = canChangeScene,
+            canShowOverlay = canShowOverlay,
+            canHideOverlay = canHideOverlay,
+            canReplaceOverlay = canReplaceOverlay,
+            onTransitionStart = onTransitionStart,
+            onTransitionEnd = onTransitionEnd,
+        )
+    }
+
+    SideEffect {
+        layoutState.transitions = transitions
+        layoutState.motionScheme = motionScheme
+        layoutState.canChangeScene = canChangeScene
+        layoutState.canShowOverlay = canShowOverlay
+        layoutState.canHideOverlay = canHideOverlay
+        layoutState.canReplaceOverlay = canReplaceOverlay
+        layoutState.onTransitionStart = onTransitionStart
+        layoutState.onTransitionEnd = onTransitionEnd
+    }
+    return layoutState
+}
+
 /** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 internal class MutableSceneTransitionLayoutStateImpl(
     initialScene: SceneKey,
+    internal var motionScheme: MotionScheme,
     override var transitions: SceneTransitions = transitions {},
     initialOverlays: Set<OverlayKey> = emptySet(),
-    internal val canChangeScene: (SceneKey) -> Boolean = { true },
-    internal val canShowOverlay: (OverlayKey) -> Boolean = { true },
-    internal val canHideOverlay: (OverlayKey) -> Boolean = { true },
-    internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
+    internal var canChangeScene: (SceneKey) -> Boolean = { true },
+    internal var canShowOverlay: (OverlayKey) -> Boolean = { true },
+    internal var canHideOverlay: (OverlayKey) -> Boolean = { true },
+    internal var canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
         true
     },
-    private val onTransitionStart: (TransitionState.Transition) -> Unit = {},
-    private val onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+    internal var onTransitionStart: (TransitionState.Transition) -> Unit = {},
+    internal var onTransitionEnd: (TransitionState.Transition) -> Unit = {},
 ) : MutableSceneTransitionLayoutState {
     private val creationThread: Thread = Thread.currentThread()
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index d50304d4..52c9ddc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -17,10 +17,7 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.snap
-import androidx.compose.animation.core.spring
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
@@ -34,7 +31,6 @@
 /** The transitions configuration of a [SceneTransitionLayout]. */
 class SceneTransitions
 internal constructor(
-    internal val defaultMotionSpatialSpec: SpringSpec<Float>,
     internal val transitionSpecs: List<TransitionSpecImpl>,
     internal val interruptionHandler: InterruptionHandler,
 ) {
@@ -123,16 +119,8 @@
         )
 
     companion object {
-        internal val DefaultSwipeSpec =
-            spring(
-                stiffness = Spring.StiffnessMediumLow,
-                dampingRatio = Spring.DampingRatioLowBouncy,
-                visibilityThreshold = OffsetVisibilityThreshold,
-            )
-
         val Empty =
             SceneTransitions(
-                defaultMotionSpatialSpec = DefaultSwipeSpec,
                 transitionSpecs = emptyList(),
                 interruptionHandler = DefaultInterruptionHandler,
             )
@@ -188,15 +176,7 @@
      * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
      * the transition is triggered (i.e. it is not gesture-based).
      */
-    val progressSpec: AnimationSpec<Float>
-
-    /**
-     * The [SpringSpec] used to animate the associated transition progress when the transition was
-     * started by a swipe and is now animating back to a scene because the user lifted their finger.
-     *
-     * If `null`, then the [SceneTransitions.defaultMotionSpatialSpec] will be used.
-     */
-    val motionSpatialSpec: AnimationSpec<Float>?
+    val progressSpec: AnimationSpec<Float>?
 
     /**
      * The distance it takes for this transition to animate from 0% to 100% when it is driven by a
@@ -213,7 +193,6 @@
         internal val Empty =
             TransformationSpecImpl(
                 progressSpec = snap(),
-                motionSpatialSpec = null,
                 distance = null,
                 transformationMatchers = emptyList(),
             )
@@ -246,7 +225,6 @@
                 val reverse = transformationSpec.invoke(transition)
                 TransformationSpecImpl(
                     progressSpec = reverse.progressSpec,
-                    motionSpatialSpec = reverse.motionSpatialSpec,
                     distance = reverse.distance,
                     transformationMatchers =
                         reverse.transformationMatchers.map {
@@ -275,8 +253,7 @@
  * [ElementTransformations].
  */
 internal class TransformationSpecImpl(
-    override val progressSpec: AnimationSpec<Float>,
-    override val motionSpatialSpec: SpringSpec<Float>?,
+    override val progressSpec: AnimationSpec<Float>?,
     override val distance: UserActionDistance?,
     override val transformationMatchers: List<TransformationMatcher>,
 ) : TransformationSpec {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 4137f5f..3bd37ad 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
+
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -364,10 +367,7 @@
 
         check(isAnimatingOffset())
 
-        val motionSpatialSpec =
-            spec
-                ?: contentTransition.transformationSpec.motionSpatialSpec
-                ?: layoutState.transitions.defaultMotionSpatialSpec
+        val motionSpatialSpec = spec ?: layoutState.motionScheme.defaultSpatialSpec()
 
         val velocityConsumed = CompletableDeferred<Float>()
         offsetAnimationRunnable.complete {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 776d553..a29c1bb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -19,7 +19,6 @@
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.Easing
 import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.SpringSpec
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
@@ -37,12 +36,6 @@
 @TransitionDsl
 interface SceneTransitionsBuilder {
     /**
-     * The default [AnimationSpec] used when after the user lifts their finger after starting a
-     * swipe to transition, to animate back into one of the 2 scenes we are transitioning to.
-     */
-    var defaultMotionSpatialSpec: SpringSpec<Float>
-
-    /**
      * The [InterruptionHandler] used when transitions are interrupted. Defaults to
      * [DefaultInterruptionHandler].
      */
@@ -139,15 +132,7 @@
      * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
      * the transition is triggered (i.e. it is not gesture-based).
      */
-    var spec: AnimationSpec<Float>
-
-    /**
-     * The [SpringSpec] used to animate the associated transition progress when the transition was
-     * started by a swipe and is now animating back to a scene because the user lifted their finger.
-     *
-     * If `null`, then the [SceneTransitionsBuilder.defaultMotionSpatialSpec] will be used.
-     */
-    var motionSpatialSpec: SpringSpec<Float>?
+    var spec: AnimationSpec<Float>?
 
     /** The CUJ associated to this transitions. */
     @CujType var cuj: Int?
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 9a9b05e..ccb7024 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -19,10 +19,7 @@
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.DurationBasedAnimationSpec
 import androidx.compose.animation.core.Easing
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.VectorConverter
-import androidx.compose.animation.core.spring
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.Dp
 import com.android.compose.animation.scene.content.state.TransitionState
@@ -42,14 +39,12 @@
 internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
     val impl = SceneTransitionsBuilderImpl().apply(builder)
     return SceneTransitions(
-        defaultMotionSpatialSpec = impl.defaultMotionSpatialSpec,
         transitionSpecs = impl.transitionSpecs,
         interruptionHandler = impl.interruptionHandler,
     )
 }
 
 private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
-    override var defaultMotionSpatialSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec
     override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler
 
     val transitionSpecs = mutableListOf<TransitionSpecImpl>()
@@ -109,7 +104,6 @@
             val impl = TransitionBuilderImpl(transition).apply(builder)
             return TransformationSpecImpl(
                 progressSpec = impl.spec,
-                motionSpatialSpec = impl.motionSpatialSpec,
                 distance = impl.distance,
                 transformationMatchers = impl.transformationMatchers,
             )
@@ -212,8 +206,7 @@
 
 internal class TransitionBuilderImpl(override val transition: TransitionState.Transition) :
     BaseTransitionBuilderImpl(), TransitionBuilder {
-    override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
-    override var motionSpatialSpec: SpringSpec<Float>? = null
+    override var spec: AnimationSpec<Float>? = null
     override var distance: UserActionDistance? = null
     override var cuj: Int? = null
     private val durationMillis: Int by lazy {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index c61bb6e..6ccd498 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -17,8 +17,12 @@
 package com.android.compose.animation.scene.content
 
 import android.annotation.SuppressLint
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.AnimationVector
+import androidx.compose.animation.core.TwoWayConverter
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
@@ -123,16 +127,25 @@
     override val lookaheadScope: LookaheadScope
         get() = layoutImpl.lookaheadScope
 
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    private val animationSpatialSpec =
+        object : AnimationSpec<Float> {
+            override fun <V : AnimationVector> vectorize(converter: TwoWayConverter<Float, V>) =
+                layoutImpl.state.motionScheme.defaultSpatialSpec<Float>().vectorize(converter)
+        }
+
     private val _verticalOverscrollEffect =
         OffsetOverscrollEffect(
             orientation = Orientation.Vertical,
             animationScope = layoutImpl.animationScope,
+            animationSpec = animationSpatialSpec,
         )
 
     private val _horizontalOverscrollEffect =
         OffsetOverscrollEffect(
             orientation = Orientation.Horizontal,
             animationScope = layoutImpl.animationScope,
+            animationSpec = animationSpatialSpec,
         )
 
     val verticalOverscrollGestureEffect = GestureEffect(_verticalOverscrollEffect)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index e9542c8..e23e234 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -18,8 +18,7 @@
 
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
@@ -385,14 +384,13 @@
             fun create(): Animatable<Float, AnimationVector1D> {
                 val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold)
                 layoutImpl.animationScope.launch {
-                    val motionSpatialSpec = layoutImpl.state.transitions.defaultMotionSpatialSpec
-                    val progressSpec =
-                        spring(
-                            stiffness = motionSpatialSpec.stiffness,
-                            dampingRatio = Spring.DampingRatioNoBouncy,
-                            visibilityThreshold = ProgressVisibilityThreshold,
-                        )
-                    animatable.animateTo(0f, progressSpec)
+                    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+                    animatable.animateTo(
+                        targetValue = 0f,
+                        // Quickly animate (use fast) the current transition and without bounces
+                        // (use effects). A new transition will start soon.
+                        animationSpec = layoutImpl.state.motionScheme.fastEffectsSpec(),
+                    )
                 }
 
                 return animatable
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
index 00cd0ca..2134510 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
@@ -20,7 +20,6 @@
 import androidx.compose.animation.core.DeferredTargetAnimation
 import androidx.compose.animation.core.ExperimentalAnimatableApi
 import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.spring
 import androidx.compose.ui.unit.Dp
@@ -103,14 +102,6 @@
     // The spring animating the alpha of the container.
     val alphaSpec = spring<Float>(stiffness = 1200f, dampingRatio = 0.99f)
 
-    // The spring animating the progress when releasing the finger.
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            dampingRatio = Spring.DampingRatioNoBouncy,
-            visibilityThreshold = 0.5f,
-        )
-
     // Size transformation.
     transformation(container) {
         VerticalContainerRevealSizeTransformation(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 62ec221..d11e6da1 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -386,7 +386,7 @@
     @Test
     fun animatedValueIsUsingLastTransition() = runTest {
         val state =
-            rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA, transitions {}) }
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA, transitions {}) }
 
         val foo = ValueKey("foo")
         val bar = ValueKey("bar")
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
index 06a9735..dc69426 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
@@ -42,7 +42,7 @@
         lateinit var layoutImpl: SceneTransitionLayoutImpl
         rule.setContent {
             SceneTransitionLayoutForTesting(
-                remember { MutableSceneTransitionLayoutState(SceneA) },
+                remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
                 onLayoutImpl = { layoutImpl = it },
             ) {
                 scene(SceneA) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index ef36077..35ff004 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -60,7 +60,7 @@
     private class TestGestureScope(val testScope: MonotonicClockTestScope) {
         var canChangeScene: (SceneKey) -> Boolean = { true }
         val layoutState =
-            MutableSceneTransitionLayoutStateImpl(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 EmptyTestTransitions,
                 canChangeScene = { canChangeScene(it) },
@@ -640,10 +640,7 @@
     @Test
     fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
         // Make scene B overscrollable.
-        layoutState.transitions = transitions {
-            defaultMotionSpatialSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
-            from(SceneA, to = SceneB) {}
-        }
+        layoutState.transitions = transitions { from(SceneA, to = SceneB) {} }
 
         val dragController =
             onDragStarted(
@@ -671,10 +668,7 @@
     @Test
     fun overscroll_releaseBetween0And100Percent_down() = runGestureTest {
         // Make scene C overscrollable.
-        layoutState.transitions = transitions {
-            defaultMotionSpatialSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
-            from(SceneA, to = SceneC) {}
-        }
+        layoutState.transitions = transitions { from(SceneA, to = SceneC) {} }
 
         val dragController =
             onDragStarted(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index b405fbe..8d0af9b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -209,7 +209,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) { spec = tween }
@@ -343,7 +343,7 @@
 
     @Test
     fun elementIsReusedBetweenScenes() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var sceneCState by mutableStateOf(0)
         val key = TestElements.Foo
         var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -473,7 +473,7 @@
 
     @Test
     fun elementModifierSupportsUpdates() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var key by mutableStateOf(TestElements.Foo)
         var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
 
@@ -523,7 +523,7 @@
             rule.waitUntil(timeoutMillis = 10_000) { animationFinished }
         }
 
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         rule.setContent {
             scrollScope = rememberCoroutineScope()
 
@@ -618,7 +618,7 @@
     fun layoutGetsCurrentTransitionStateFromComposition() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) {
@@ -663,7 +663,8 @@
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+        val state =
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
         rule.setContent {
             density = LocalDensity.current
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -718,7 +719,8 @@
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+        val state =
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
         rule.setContent {
             density = LocalDensity.current
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -813,7 +815,8 @@
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+        val state =
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
         rule.setContent {
             density = LocalDensity.current
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -866,7 +869,8 @@
         val layoutWidth = 200.dp
         val layoutHeight = 400.dp
 
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+        val state =
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
 
         rule.setContent {
             density = LocalDensity.current
@@ -934,7 +938,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     initialScene = SceneA,
                     transitions =
                         transitions {
@@ -943,7 +947,6 @@
                             }
                         },
                 )
-                    as MutableSceneTransitionLayoutStateImpl
             }
 
         rule.setContent {
@@ -999,7 +1002,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         // Foo is at the top left corner of scene A. We make it disappear during A
@@ -1126,7 +1129,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) }
@@ -1331,7 +1334,7 @@
         val fooSize = 100.dp
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) }
@@ -1439,7 +1442,7 @@
     @Test
     fun targetStateIsSetEvenWhenNotPlaced() {
         // Start directly at A => B but with progress < 0f to overscroll on A.
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         lateinit var layoutImpl: SceneTransitionLayoutImpl
         val scope =
@@ -1473,7 +1476,7 @@
     fun lastAlphaIsNotSetByOutdatedLayer() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } },
                 )
@@ -1537,7 +1540,7 @@
     fun fadingElementsDontAppearInstantly() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } },
                 )
@@ -1583,7 +1586,7 @@
 
     @Test
     fun lastPlacementValuesAreClearedOnNestedElements() {
-        val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         @Composable
         fun ContentScope.NestedFooBar() {
@@ -1658,7 +1661,7 @@
     fun currentTransitionSceneIsUsedToComputeElementValues() {
         val state =
             rule.runOnIdle {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneB, to = SceneC) {
@@ -1709,7 +1712,7 @@
 
     @Test
     fun interruptionDeltasAreProperlyCleaned() {
-        val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         @Composable
         fun ContentScope.Foo(offset: Dp) {
@@ -1780,7 +1783,7 @@
     fun transparentElementIsNotImpactingInterruption() {
         val state =
             rule.runOnIdle {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) {
@@ -1856,7 +1859,7 @@
 
     @Test
     fun replacedTransitionDoesNotTriggerInterruption() {
-        val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         @Composable
         fun ContentScope.Foo(modifier: Modifier = Modifier) {
@@ -2027,7 +2030,7 @@
     ): SceneTransitionLayoutImpl {
         val state =
             rule.runOnIdle {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     from,
                     transitions { from(from, to = to, preview = preview, builder = transition) },
                 )
@@ -2174,7 +2177,7 @@
             )
         }
 
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         val scope =
             rule.setContentAndCreateMainScope {
                 SceneTransitionLayout(state) {
@@ -2215,7 +2218,7 @@
             Box(modifier.element(TestElements.Foo).size(50.dp))
         }
 
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         val scope =
             rule.setContentAndCreateMainScope {
                 SceneTransitionLayout(state) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
index 3622369..b44f552 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
@@ -39,7 +39,7 @@
     @Test
     fun default() = runMonotonicClockTest {
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions { /* default interruption handler */ },
             )
@@ -62,7 +62,7 @@
     @Test
     fun chainingDisabled() = runMonotonicClockTest {
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions {
                     // Handler that animates from currentScene (default) but disables chaining.
@@ -97,7 +97,7 @@
     fun animateFromOtherScene() = runMonotonicClockTest {
         val duration = 500
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions {
                     // Handler that animates from the scene that is not currentScene.
@@ -146,7 +146,7 @@
 
     @Test
     fun animateToFromScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {})
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions {})
 
         // Fake a transition from A to B that has a non 0 velocity.
         val progressVelocity = 1f
@@ -182,7 +182,7 @@
 
     @Test
     fun animateToToScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {})
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions {})
 
         // Fake a transition from A to B with current scene = A that has a non 0 velocity.
         val progressVelocity = -1f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 9e1bae5..8d718c1 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -380,7 +380,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     initialScene = SceneA,
                     initialOverlays = setOf(OverlayA),
                 )
@@ -420,7 +420,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     initialScene = SceneA,
                     initialOverlays = setOf(OverlayA),
                 )
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt
new file mode 100644
index 0000000..4326ec9
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2025 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.compose.animation.scene
+
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MotionScheme
+import com.android.compose.animation.scene.content.state.TransitionState
+
+internal fun MutableSceneTransitionLayoutStateForTests(
+    initialScene: SceneKey,
+    transitions: SceneTransitions = SceneTransitions.Empty,
+    initialOverlays: Set<OverlayKey> = emptySet(),
+    canChangeScene: (SceneKey) -> Boolean = { true },
+    canShowOverlay: (OverlayKey) -> Boolean = { true },
+    canHideOverlay: (OverlayKey) -> Boolean = { true },
+    canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
+    onTransitionStart: (TransitionState.Transition) -> Unit = {},
+    onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+): MutableSceneTransitionLayoutStateImpl {
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    return MutableSceneTransitionLayoutStateImpl(
+        initialScene,
+        motionScheme = MotionScheme.standard(),
+        transitions,
+        initialOverlays,
+        canChangeScene,
+        canShowOverlay,
+        canHideOverlay,
+        canReplaceOverlay,
+        onTransitionStart,
+        onTransitionEnd,
+    )
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
index 596e2cd..f2c0ca5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -47,7 +47,9 @@
     @Test
     fun testObservableTransitionState() = runTest {
         val state =
-            rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
+            }
 
         // Collect the current observable state into [observableState].
         // TODO(b/290184746): Use collectValues {} once it is extracted into a library that can be
@@ -106,7 +108,7 @@
     @Test
     fun observableCurrentScene() = runTestWithSnapshots {
         val state =
-            MutableSceneTransitionLayoutStateImpl(
+            MutableSceneTransitionLayoutStateForTests(
                 initialScene = SceneA,
                 transitions = transitions {},
             )
@@ -150,7 +152,7 @@
     fun testObservablePreviewTransitionState() = runTest {
         val layoutState =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions = transitions { from(SceneA, to = SceneB, preview = {}) },
                 )
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index 93fa516..50bfbfe 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -73,7 +73,7 @@
 
     @Test
     fun showThenHideOverlay() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         lateinit var coroutineScope: CoroutineScope
         rule.setContent {
             coroutineScope = rememberCoroutineScope()
@@ -115,7 +115,7 @@
 
     @Test
     fun multipleOverlays() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         lateinit var coroutineScope: CoroutineScope
         rule.setContent {
             coroutineScope = rememberCoroutineScope()
@@ -213,7 +213,7 @@
             }
         }
 
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         lateinit var coroutineScope: CoroutineScope
         rule.setContent {
             coroutineScope = rememberCoroutineScope()
@@ -285,7 +285,7 @@
     fun overlayAlignment() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+                MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
             }
         var alignment by mutableStateOf(Alignment.Center)
         rule.setContent {
@@ -320,7 +320,7 @@
     fun overlayMaxSizeIsCurrentSceneSize() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+                MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
             }
 
         val contentTag = "overlayContent"
@@ -742,7 +742,7 @@
 
     @Test
     fun overscrollingOverlay_movableElementNotInOverlay() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         val key = MovableElementKey("Foo", contents = setOf(SceneA))
         val movableElementChildTag = "movableElementChildTag"
@@ -769,7 +769,7 @@
 
     @Test
     fun overlaysAreModalByDefault() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         val scrollState = ScrollState(initial = 0)
         val scope =
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 4224a0c..9f15ebd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -50,7 +50,7 @@
 
     @Test
     fun testBack() {
-        val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         rule.setContent {
             SceneTransitionLayout(layoutState) {
                 scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
@@ -70,7 +70,7 @@
         val transitionFrames = 2
         val layoutState =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions =
                         transitions {
@@ -142,7 +142,7 @@
     fun testPredictiveBackWithPreview() {
         val layoutState =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions = transitions { from(SceneA, to = SceneB, preview = {}) },
                 )
@@ -192,7 +192,7 @@
         var canChangeSceneCalled = false
         val layoutState =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     canChangeScene = {
                         canChangeSceneCalled = true
@@ -241,7 +241,7 @@
     fun backDismissesOverlayWithHighestZIndexByDefault() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     initialOverlays = setOf(OverlayA, OverlayB),
                 )
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index f3be5e4..c5e4061 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -51,7 +51,7 @@
 
     @Test
     fun isTransitioningTo_idle() {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, SceneTransitions.Empty)
 
         assertThat(state.isTransitioning()).isFalse()
         assertThat(state.isTransitioning(from = SceneA)).isFalse()
@@ -61,7 +61,7 @@
 
     @Test
     fun isTransitioningTo_transition() = runTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, SceneTransitions.Empty)
         state.startTransitionImmediately(
             animationScope = backgroundScope,
             transition(from = SceneA, to = SceneB),
@@ -77,13 +77,13 @@
 
     @Test
     fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         assertThat(state.setTargetScene(SceneA, animationScope = this)).isNull()
     }
 
     @Test
     fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         val (transition, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
         assertThat(state.transitionState).isEqualTo(transition)
 
@@ -93,7 +93,7 @@
 
     @Test
     fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         val (_, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
         assertThat(state.setTargetScene(SceneB, animationScope = this)).isNull()
@@ -104,7 +104,7 @@
 
     @Test
     fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         assertThat(state.setTargetScene(SceneB, animationScope = this)).isNotNull()
         val (_, job) = checkNotNull(state.setTargetScene(SceneC, animationScope = this))
@@ -115,7 +115,7 @@
 
     @Test
     fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         lateinit var transition: TransitionState.Transition
         val job =
@@ -133,7 +133,7 @@
     fun setTargetScene_withTransitionKey() = runMonotonicClockTest {
         val transitionkey = TransitionKey(debugName = "foo")
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions =
                     transitions {
@@ -176,7 +176,7 @@
             return { /* do nothing */ }
         }
 
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
         val aToB = transition(SceneA, SceneB, onFreezeAndAnimate = ::onFreezeAndAnimate)
         val bToC = transition(SceneB, SceneC, onFreezeAndAnimate = ::onFreezeAndAnimate)
         val cToA = transition(SceneC, SceneA, onFreezeAndAnimate = ::onFreezeAndAnimate)
@@ -226,7 +226,7 @@
 
     @Test
     fun tooManyTransitionsLogsWtfAndClearsTransitions() = runTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
 
         fun startTransition() {
             val transition =
@@ -251,7 +251,7 @@
 
     @Test
     fun snapToScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         // Transition to B.
         state.setTargetScene(SceneB, animationScope = this)
@@ -266,7 +266,7 @@
 
     @Test
     fun snapToScene_freezesCurrentTransition() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         // Start a transition that is never finished. We don't use backgroundScope on purpose so
         // that this test would fail if the transition was not frozen when snapping.
@@ -283,7 +283,7 @@
 
     @Test
     fun seekToScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         val progress = Channel<Float>()
 
         val job =
@@ -309,7 +309,7 @@
 
     @Test
     fun seekToScene_cancelled() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         val progress = Channel<Float>()
 
         val job =
@@ -335,7 +335,7 @@
 
     @Test
     fun seekToScene_interrupted() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         val progress = Channel<Float>()
 
         val job =
@@ -354,7 +354,7 @@
 
     @Test
     fun replacedTransitionIsRemovedFromFinishedTransitions() = runTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         val aToB =
             transition(
@@ -403,7 +403,7 @@
 
     @Test
     fun transitionCanBeStartedOnlyOnce() = runTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         val transition = transition(from = SceneA, to = SceneB)
 
         state.startTransitionImmediately(backgroundScope, transition)
@@ -414,7 +414,7 @@
 
     @Test
     fun transitionFinishedWhenScopeIsEmpty() = runTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         // Start a transition.
         val transition = transition(from = SceneA, to = SceneB)
@@ -439,7 +439,7 @@
 
     @Test
     fun transitionScopeIsCancelledWhenTransitionIsForceFinished() = runTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         // Start a transition.
         val transition = transition(from = SceneA, to = SceneB)
@@ -458,7 +458,7 @@
     @Test
     fun badTransitionSpecThrowsMeaningfulMessageWhenStartingTransition() {
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions {
                     // This transition definition is bad because they both match when transitioning
@@ -483,7 +483,7 @@
 
     @Test
     fun snapToScene_multipleTransitions() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         state.startTransitionImmediately(this, transition(SceneA, SceneB))
         state.startTransitionImmediately(this, transition(SceneB, SceneC))
         state.snapToScene(SceneC)
@@ -498,7 +498,7 @@
         val finished = mutableSetOf<TransitionState.Transition>()
         val cujWhenStarting = mutableMapOf<TransitionState.Transition, Int?>()
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions {
                     // A <=> B.
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index e580e3c..3c490ae 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -17,7 +17,9 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.FiniteAnimationSpec
 import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
@@ -25,9 +27,13 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MotionScheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
@@ -60,7 +66,6 @@
 import com.android.compose.test.assertSizeIsEqualTo
 import com.android.compose.test.subjects.DpOffsetSubject
 import com.android.compose.test.subjects.assertThat
-import com.android.compose.test.transition
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import org.junit.Assert.assertThrows
@@ -88,7 +93,9 @@
     @Composable
     private fun TestContent() {
         coroutineScope = rememberCoroutineScope()
-        layoutState = remember { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
+        layoutState = remember {
+            MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
+        }
 
         SceneTransitionLayout(state = layoutState, modifier = Modifier.size(LayoutSize)) {
             scene(SceneA, userActions = mapOf(Back to SceneB)) {
@@ -317,7 +324,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) {
@@ -421,7 +428,7 @@
             assertThrows(IllegalStateException::class.java) {
                 rule.setContent {
                     SceneTransitionLayout(
-                        state = remember { MutableSceneTransitionLayoutState(SceneA) },
+                        state = remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
                         modifier = Modifier.size(LayoutSize),
                     ) {
                         // from SceneA to SceneA
@@ -436,7 +443,7 @@
 
     @Test
     fun sceneKeyInScope() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         var keyInA: ContentKey? = null
         var keyInB: ContentKey? = null
@@ -465,7 +472,7 @@
         lateinit var layoutImpl: SceneTransitionLayoutImpl
         rule.setContent {
             SceneTransitionLayoutForTesting(
-                remember { MutableSceneTransitionLayoutState(SceneA) },
+                remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
                 onLayoutImpl = { layoutImpl = it },
             ) {
                 scene(SceneA) { Box(Modifier.fillMaxSize()) }
@@ -483,7 +490,8 @@
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+        val state =
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
             SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
@@ -511,4 +519,67 @@
         // Fling animation, we are overscrolling now. Progress should always be between [0, 1].
         assertThat(transition).hasProgress(1f)
     }
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    @Test
+    fun motionSchemeArePassedToSTLState() {
+        // Implementation inspired by MotionScheme.standard()
+        @Suppress("UNCHECKED_CAST")
+        fun motionScheme(animationSpec: FiniteAnimationSpec<Any>) =
+            object : MotionScheme {
+                override fun <T> defaultEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+                override fun <T> defaultSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+
+                override fun <T> fastEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+                override fun <T> fastSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+
+                override fun <T> slowEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+                override fun <T> slowSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+            }
+
+        lateinit var state1: MutableSceneTransitionLayoutState
+        lateinit var state2: MutableSceneTransitionLayoutState
+
+        lateinit var motionScheme1: MotionScheme
+        var motionScheme2 by mutableStateOf(motionScheme(animationSpec = tween(500)))
+        rule.setContent {
+            motionScheme1 = MaterialTheme.motionScheme
+            state1 = rememberMutableSceneTransitionLayoutState(initialScene = SceneA)
+            SceneTransitionLayout(state1) {
+                scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+                    Spacer(Modifier.fillMaxSize())
+                }
+            }
+
+            MaterialTheme(motionScheme = motionScheme2) {
+                // Important: we should read this state inside the MaterialTheme composable.
+                state2 = rememberMutableSceneTransitionLayoutState(initialScene = SceneA)
+                SceneTransitionLayout(state2) {
+                    scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+                        Spacer(Modifier.fillMaxSize())
+                    }
+                }
+            }
+        }
+
+        assertThat(motionScheme1).isNotNull()
+        assertThat(motionScheme1).isNotEqualTo(motionScheme2)
+
+        assertThat((state1 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+            .isEqualTo(motionScheme1)
+
+        assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+            .isEqualTo(motionScheme2)
+
+        // Update the MaterialTheme's MotionScheme configuration.
+        motionScheme2 = motionScheme(animationSpec = spring())
+
+        // We just updated the motionScheme2 state, wait for a recomposition.
+        rule.waitForIdle()
+        assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+            .isEqualTo(motionScheme2)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index e036084..751b314 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -93,7 +93,9 @@
         initialScene: SceneKey = SceneA,
         transitions: SceneTransitions = EmptyTestTransitions,
     ): MutableSceneTransitionLayoutState {
-        return rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene, transitions) }
+        return rule.runOnUiThread {
+            MutableSceneTransitionLayoutStateForTests(initialScene, transitions)
+        }
     }
 
     /** The content under test. */
@@ -738,7 +740,7 @@
     fun startEnd_ltrLayout() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     initialScene = SceneA,
                     transitions =
                         transitions {
@@ -811,7 +813,7 @@
     fun startEnd_rtlLayout() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     initialScene = SceneA,
                     transitions =
                         transitions {
@@ -893,7 +895,9 @@
                     Text("Count: $count")
                 }
 
-                SceneTransitionLayout(remember { MutableSceneTransitionLayoutState(SceneA) }) {
+                SceneTransitionLayout(
+                    remember { MutableSceneTransitionLayoutStateForTests(SceneA) }
+                ) {
                     scene(SceneA) { Box(Modifier.fillMaxSize()) }
                 }
             }
@@ -909,7 +913,7 @@
 
     @Test
     fun swipeToSceneSupportsUpdates() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         rule.setContent {
             SceneTransitionLayout(state) {
@@ -943,7 +947,7 @@
     @Test
     fun swipeToSceneNodeIsKeptWhenDisabled() {
         var hasHorizontalActions by mutableStateOf(false)
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -983,7 +987,7 @@
 
     @Test
     fun nestedScroll_useFromSourceInfo() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1033,7 +1037,7 @@
 
     @Test
     fun nestedScroll_ignoreMouseWheel() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1057,7 +1061,7 @@
 
     @Test
     fun nestedScroll_keepPriorityEvenIfWeCanNoLongerScrollOnThatDirection() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1097,7 +1101,7 @@
     fun nestedScroll_replaceOverlay() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+                MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
             }
         var touchSlop = 0f
         rule.setContent {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index aada4a50..e098aac 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -17,9 +17,7 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.CubicBezierEasing
-import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.TweenSpec
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import androidx.compose.ui.unit.IntSize
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -84,7 +82,7 @@
     fun defaultTransitionSpec() {
         val transitions = transitions { from(SceneA, to = SceneB) }
         val transformationSpec = transitions.transitionSpecs.single().transformationSpec(aToB())
-        assertThat(transformationSpec.progressSpec).isInstanceOf(SpringSpec::class.java)
+        assertThat(transformationSpec.progressSpec).isNull()
     }
 
     @Test
@@ -272,44 +270,10 @@
     }
 
     @Test
-    fun springSpec() {
-        val defaultSpec = spring<Float>(stiffness = 1f)
-        val specFromAToC = spring<Float>(stiffness = 2f)
-        val transitions = transitions {
-            defaultMotionSpatialSpec = defaultSpec
-
-            from(SceneA, to = SceneB) {
-                // Default swipe spec.
-            }
-            from(SceneA, to = SceneC) { motionSpatialSpec = specFromAToC }
-        }
-
-        assertThat(transitions.defaultMotionSpatialSpec).isSameInstanceAs(defaultSpec)
-
-        // A => B does not have a custom spec.
-        assertThat(
-                transitions
-                    .transitionSpec(from = SceneA, to = SceneB, key = null)
-                    .transformationSpec(aToB())
-                    .motionSpatialSpec
-            )
-            .isNull()
-
-        // A => C has a custom swipe spec.
-        assertThat(
-                transitions
-                    .transitionSpec(from = SceneA, to = SceneC, key = null)
-                    .transformationSpec(transition(from = SceneA, to = SceneC))
-                    .motionSpatialSpec
-            )
-            .isSameInstanceAs(specFromAToC)
-    }
-
-    @Test
     fun transitionIsPassedToBuilder() = runTest {
         var transitionPassedToBuilder: TransitionState.Transition? = null
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions { from(SceneA, to = SceneB) { transitionPassedToBuilder = transition } },
             )
@@ -340,7 +304,7 @@
             }
         }
 
-        val state = MutableSceneTransitionLayoutState(SceneA, transitions)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions)
         assertThrows(IllegalStateException::class.java) {
             runBlocking { state.startTransition(transition(from = SceneA, to = SceneB)) }
         }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
index 0da422b..bb511bc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
@@ -37,6 +37,7 @@
 import com.android.compose.animation.scene.ContentScope
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
 import com.android.compose.animation.scene.Scale
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
@@ -104,7 +105,9 @@
         startScene: SceneKey,
         transitions: SceneTransitions = SceneTransitions.Empty,
     ): MutableSceneTransitionLayoutState {
-        return rule.runOnUiThread { MutableSceneTransitionLayoutState(startScene, transitions) }
+        return rule.runOnUiThread {
+            MutableSceneTransitionLayoutStateForTests(startScene, transitions)
+        }
     }
 
     private val threeNestedStls:
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
index d8b7136..83dd6d3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
@@ -39,6 +39,7 @@
 import com.android.compose.animation.scene.Default4FrameLinearTransition
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TestElements
 import com.android.compose.animation.scene.TestScenes
@@ -94,7 +95,7 @@
 
     private val nestedState: MutableSceneTransitionLayoutState =
         rule.runOnUiThread {
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 Scenes.NestedSceneA,
                 transitions {
                     from(
@@ -108,7 +109,7 @@
 
     private val nestedNestedState: MutableSceneTransitionLayoutState =
         rule.runOnUiThread {
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 Scenes.NestedNestedSceneA,
                 transitions {
                     from(
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
index 5cccfb1..6d47bab 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
@@ -29,6 +29,6 @@
     currentScene: SceneKey = remember { SceneKey("current") },
     content: @Composable ContentScope.() -> Unit,
 ) {
-    val state = remember { MutableSceneTransitionLayoutState(currentScene) }
+    val state = rememberMutableSceneTransitionLayoutState(currentScene)
     SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
 }
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index bc160fc..f94a7ed 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -129,13 +129,12 @@
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        state =
-            runOnUiThread {
-                MutableSceneTransitionLayoutState(
-                    fromScene,
-                    transitions { from(fromScene, to = toScene, builder = transition) },
-                )
-            },
+        state = {
+            rememberMutableSceneTransitionLayoutState(
+                fromScene,
+                transitions { from(fromScene, to = toScene, builder = transition) },
+            )
+        },
         changeState = changeState,
         transitionLayout = { state ->
             SceneTransitionLayout(state, layoutModifier) {
@@ -157,13 +156,12 @@
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        state =
-            runOnUiThread {
-                MutableSceneTransitionLayoutState(
-                    fromScene,
-                    transitions = transitions { from(fromScene, overlay, builder = transition) },
-                )
-            },
+        state = {
+            rememberMutableSceneTransitionLayoutState(
+                fromScene,
+                transitions = transitions { from(fromScene, overlay, builder = transition) },
+            )
+        },
         transitionLayout = { state ->
             SceneTransitionLayout(state) {
                 scene(fromScene) { fromSceneContent() }
@@ -185,14 +183,13 @@
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        state =
-            runOnUiThread {
-                MutableSceneTransitionLayoutState(
-                    toScene,
-                    initialOverlays = setOf(overlay),
-                    transitions = transitions { from(overlay, toScene, builder = transition) },
-                )
-            },
+        state = {
+            rememberMutableSceneTransitionLayoutState(
+                toScene,
+                initialOverlays = setOf(overlay),
+                transitions = transitions { from(overlay, toScene, builder = transition) },
+            )
+        },
         transitionLayout = { state ->
             SceneTransitionLayout(state) {
                 scene(toScene) { toSceneContent() }
@@ -218,14 +215,13 @@
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        state =
-            runOnUiThread {
-                MutableSceneTransitionLayoutState(
-                    currentScene,
-                    initialOverlays = setOf(from),
-                    transitions = transitions { from(from, to, builder = transition) },
-                )
-            },
+        state = {
+            rememberMutableSceneTransitionLayoutState(
+                currentScene,
+                initialOverlays = setOf(from),
+                transitions = transitions { from(from, to, builder = transition) },
+            )
+        },
         transitionLayout = { state ->
             SceneTransitionLayout(state) {
                 scene(currentScene) { currentSceneContent() }
@@ -263,16 +259,14 @@
     fromScene: SceneKey = TestScenes.SceneA,
     toScene: SceneKey = TestScenes.SceneB,
 ): RecordedMotion {
-    val state =
-        toolkit.composeContentTestRule.runOnUiThread {
-            MutableSceneTransitionLayoutState(
-                fromScene,
-                transitions { from(fromScene, to = toScene, builder = transition) },
-            )
-        }
-
+    lateinit var state: MutableSceneTransitionLayoutState
     return recordMotion(
         content = { play ->
+            state =
+                rememberMutableSceneTransitionLayoutState(
+                    fromScene,
+                    transitions { from(fromScene, to = toScene, builder = transition) },
+                )
             LaunchedEffect(play) {
                 if (play) {
                     state.setTargetScene(toScene, animationScope = this)
@@ -309,7 +303,7 @@
     }
 
     testTransition(
-        state = state,
+        state = { state },
         changeState = { state -> state.setTargetScene(to, animationScope = this) },
         transitionLayout = transitionLayout,
         builder = builder,
@@ -323,7 +317,7 @@
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        state = states[0],
+        state = { states[0] },
         changeState = { changeState(states) },
         transitionLayout = { transitionLayout(states) },
         builder = builder,
@@ -331,16 +325,18 @@
 }
 
 /** Test the transition from [state] to [to]. */
-fun ComposeContentTestRule.testTransition(
-    state: MutableSceneTransitionLayoutState,
+private fun ComposeContentTestRule.testTransition(
+    state: @Composable () -> MutableSceneTransitionLayoutState,
     changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit,
     transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     lateinit var coroutineScope: CoroutineScope
+    lateinit var layoutState: MutableSceneTransitionLayoutState
     setContent {
+        layoutState = state()
         coroutineScope = rememberCoroutineScope()
-        transitionLayout(state)
+        transitionLayout(layoutState)
     }
 
     val assertionScope =
@@ -390,7 +386,7 @@
     mainClock.autoAdvance = false
 
     // Change the current scene.
-    runOnUiThread { coroutineScope.changeState(state) }
+    runOnUiThread { coroutineScope.changeState(layoutState) }
     waitForIdle()
     mainClock.advanceTimeByFrame()
     waitForIdle()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 69fb03d..4e8a2a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -22,8 +22,12 @@
 import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT
 import android.content.Intent
 import android.content.pm.UserInfo
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_ANY_BIOMETRIC
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
 import android.hardware.biometrics.BiometricManager
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
+import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -75,6 +79,8 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.stub
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -153,7 +159,7 @@
     fun fingerprintEnrollmentChange() =
         testScope.runTest {
             createBiometricSettingsRepository()
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
             val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
             runCurrent()
 
@@ -173,7 +179,7 @@
     fun fingerprintEnabledStateChange() =
         testScope.runTest {
             createBiometricSettingsRepository()
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
             val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
             runCurrent()
 
@@ -183,21 +189,59 @@
             assertThat(fingerprintAllowed()).isTrue()
 
             // when biometrics are not enabled by settings
-            biometricsAreNotEnabledBySettings()
+            biometricsAreNotEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
             assertThat(fingerprintAllowed()).isFalse()
 
             // when biometrics are enabled by settings
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
             assertThat(fingerprintAllowed()).isTrue()
         }
 
     @Test
+    @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    fun enabledStateChange_typeFingerprintEnabled_typeFaceDisabled() =
+        testScope.runTest {
+            createBiometricSettingsRepository()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
+            val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
+            val faceAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+            runCurrent()
+
+            // start state
+            authController.stub {
+                on { isFingerprintEnrolled(anyInt()) } doReturn true
+            }
+            enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
+            assertThat(fingerprintAllowed()).isTrue()
+            assertThat(faceAllowed()).isFalse()
+        }
+
+    @Test
+    @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    fun enabledStateChange_typeFingerprintDisabled_typeFaceEnabled() =
+        testScope.runTest {
+            createBiometricSettingsRepository()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
+            val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
+            val faceAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+            runCurrent()
+
+            // start state
+            authController.stub {
+                on { isFaceAuthEnrolled(anyInt()) } doReturn true
+            }
+            enrollmentChange(FACE, PRIMARY_USER_ID, true)
+            assertThat(fingerprintAllowed()).isFalse()
+            assertThat(faceAllowed()).isTrue()
+        }
+
+    @Test
     fun strongBiometricAllowedChange() =
         testScope.runTest {
             fingerprintIsEnrolled()
             doNotDisableKeyguardAuthFeatures()
             createBiometricSettingsRepository()
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
 
             val strongBiometricAllowed by
                 collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
@@ -220,7 +264,7 @@
             createBiometricSettingsRepository()
             val convenienceFaceAuthAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
             doNotDisableKeyguardAuthFeatures()
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
             onNonStrongAuthChanged(true, PRIMARY_USER_ID)
@@ -282,7 +326,7 @@
             faceAuthIsEnrolled()
             createBiometricSettingsRepository()
             doNotDisableKeyguardAuthFeatures()
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
             runCurrent()
 
             val convenienceBiometricAllowed by
@@ -315,7 +359,7 @@
         testScope.runTest {
             fingerprintIsEnrolled(PRIMARY_USER_ID)
             createBiometricSettingsRepository()
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
 
             val fingerprintEnabledByDevicePolicy =
                 collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
@@ -341,7 +385,7 @@
             createBiometricSettingsRepository()
             val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
 
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
 
             doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
 
@@ -376,16 +420,22 @@
             assertThat(faceAuthAllowed()).isTrue()
         }
 
-    private fun biometricsAreEnabledBySettings(userId: Int = PRIMARY_USER_ID) {
+    private fun biometricsAreEnabledBySettings(
+        userId: Int = PRIMARY_USER_ID,
+        modality: Int = TYPE_ANY_BIOMETRIC,
+    ) {
         verify(biometricManager, atLeastOnce())
             .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
-        biometricManagerCallback.value.onChanged(true, userId)
+        biometricManagerCallback.value.onChanged(true, userId, modality)
     }
 
-    private fun biometricsAreNotEnabledBySettings(userId: Int = PRIMARY_USER_ID) {
+    private fun biometricsAreNotEnabledBySettings(
+        userId: Int = PRIMARY_USER_ID,
+        modality: Int = TYPE_ANY_BIOMETRIC,
+    ) {
         verify(biometricManager, atLeastOnce())
             .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
-        biometricManagerCallback.value.onChanged(false, userId)
+        biometricManagerCallback.value.onChanged(false, userId, modality)
     }
 
     @Test
@@ -413,7 +463,7 @@
 
             userRepository.setSelectedUserInfo(ANOTHER_USER)
             doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID)
-            biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
+            biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID, TYPE_FACE)
             onNonStrongAuthChanged(true, ANOTHER_USER_ID)
             whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
             enrollmentChange(FACE, ANOTHER_USER_ID, true)
@@ -441,7 +491,7 @@
 
             assertThat(isFaceAuthAllowed()).isFalse()
 
-            biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
+            biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID, TYPE_FACE)
             runCurrent()
             assertThat(isFaceAuthAllowed()).isFalse()
 
@@ -458,7 +508,7 @@
             faceAuthIsEnrolled()
             createBiometricSettingsRepository()
 
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
             doNotDisableKeyguardAuthFeatures()
             mobileConnectionsRepository.fake.isAnySimSecure.value = false
             runCurrent()
@@ -485,7 +535,7 @@
             deviceIsInPostureThatSupportsFaceAuth()
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsStrongBiometric()
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
             mobileConnectionsRepository.fake.isAnySimSecure.value = false
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
@@ -512,12 +562,12 @@
             assertThat(isFaceAuthAllowed()).isFalse()
 
             // Value changes for another user
-            biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
+            biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID, TYPE_FACE)
 
             assertThat(isFaceAuthAllowed()).isFalse()
 
             // Value changes for current user.
-            biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
+            biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID, TYPE_FACE)
 
             assertThat(isFaceAuthAllowed()).isTrue()
         }
@@ -537,13 +587,13 @@
                 .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
             val isFingerprintEnrolledAndEnabled =
                 collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
-            biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
+            biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID, TYPE_FINGERPRINT)
             runCurrent()
             userRepository.setSelectedUserInfo(ANOTHER_USER)
             runCurrent()
             assertThat(isFingerprintEnrolledAndEnabled()).isFalse()
 
-            biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
+            biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID, TYPE_FINGERPRINT)
             runCurrent()
             userRepository.setSelectedUserInfo(PRIMARY_USER)
             runCurrent()
@@ -559,7 +609,7 @@
             verify(biometricManager)
                 .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
             val isFaceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
-            biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
+            biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID, TYPE_FACE)
             runCurrent()
 
             userRepository.setSelectedUserInfo(ANOTHER_USER)
@@ -691,7 +741,7 @@
             deviceIsInPostureThatSupportsFaceAuth()
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsStrongBiometric()
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
             onNonStrongAuthChanged(false, PRIMARY_USER_ID)
@@ -715,7 +765,7 @@
             deviceIsInPostureThatSupportsFaceAuth()
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsNonStrongBiometric()
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
             onNonStrongAuthChanged(false, PRIMARY_USER_ID)
@@ -737,7 +787,7 @@
     fun fpAuthCurrentlyAllowed_dependsOnNonStrongAuthBiometricSetting_ifFpIsNotStrong() =
         testScope.runTest {
             createBiometricSettingsRepository()
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
             val isFingerprintCurrentlyAllowed by
                 collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
 
@@ -779,7 +829,7 @@
     fun fpAuthCurrentlyAllowed_dependsOnStrongAuthBiometricSetting_ifFpIsStrong() =
         testScope.runTest {
             createBiometricSettingsRepository()
-            biometricsAreEnabledBySettings()
+            biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
             val isFingerprintCurrentlyAllowed by
                 collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
 
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 71b622a..9b852df 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -61,6 +61,8 @@
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.plugins.clocks.ZenData
 import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
 import com.android.systemui.res.R as SysuiR
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.settings.UserTracker
@@ -106,6 +108,7 @@
     private val zenModeController: ZenModeController,
     private val zenModeInteractor: ZenModeInteractor,
     private val userTracker: UserTracker,
+    private val powerInteractor: PowerInteractor,
 ) {
     var loggers =
         listOf(
@@ -377,13 +380,13 @@
             override fun onTimeChanged() {
                 refreshTime()
             }
-
-            private fun refreshTime() {
-                clock?.smallClock?.events?.onTimeTick()
-                clock?.largeClock?.events?.onTimeTick()
-            }
         }
 
+    private fun refreshTime() {
+        clock?.smallClock?.events?.onTimeTick()
+        clock?.largeClock?.events?.onTimeTick()
+    }
+
     @VisibleForTesting
     internal fun listenForDnd(scope: CoroutineScope): Job {
         ModesUi.assertInNewMode()
@@ -474,6 +477,7 @@
                     listenForAnyStateToAodTransition(this)
                     listenForAnyStateToLockscreenTransition(this)
                     listenForAnyStateToDozingTransition(this)
+                    listenForScreenPowerOn(this)
                 }
             }
         smallTimeListener?.update(shouldTimeListenerRun)
@@ -643,6 +647,17 @@
         }
     }
 
+    @VisibleForTesting
+    internal fun listenForScreenPowerOn(scope: CoroutineScope): Job {
+        return scope.launch {
+            powerInteractor.screenPowerState.collect { powerState ->
+                if (powerState != ScreenPowerState.SCREEN_OFF) {
+                    refreshTime()
+                }
+            }
+        }
+    }
+
     class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
         val predrawListener =
             ViewTreeObserver.OnPreDrawListener {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 7d291c3..61038ef 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -437,8 +437,12 @@
     private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
             new IBiometricEnabledOnKeyguardCallback.Stub() {
                 @Override
-                public void onChanged(boolean enabled, int userId) {
+                public void onChanged(boolean enabled, int userId, int modality) {
                     mHandler.post(() -> {
+                        if (com.android.settings.flags.Flags.biometricsOnboardingEducation()
+                                && modality != TYPE_FINGERPRINT) {
+                            return;
+                        }
                         mBiometricEnabledForUser.put(userId, enabled);
                         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
                     });
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index ab8cc71..4e7de5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -20,6 +20,9 @@
 import android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
 import android.content.Context
 import android.content.IntentFilter
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE
 import android.hardware.biometrics.BiometricManager
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
 import android.os.UserHandle
@@ -141,6 +144,8 @@
 ) : BiometricSettingsRepository, Dumpable {
 
     private val biometricsEnabledForUser = mutableMapOf<Int, Boolean>()
+    private val fingerprintEnabledForUser = mutableMapOf<Int, Boolean>()
+    private val faceEnabledForUser = mutableMapOf<Int, Boolean>()
 
     override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
 
@@ -246,10 +251,25 @@
             }
         }
 
-    private val areBiometricsEnabledForCurrentUser: Flow<Boolean> =
+    private val isFingerprintEnabledForCurrentUser: Flow<Boolean> =
         userRepository.selectedUserInfo.flatMapLatest { userInfo ->
             areBiometricsEnabledForDeviceEntryFromUserSetting.map {
-                biometricsEnabledForUser[userInfo.id] ?: false
+                if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+                    fingerprintEnabledForUser[userInfo.id] ?: false
+                } else {
+                    biometricsEnabledForUser[userInfo.id] ?: false
+                }
+            }
+        }
+
+    private val isFaceEnabledForCurrentUser: Flow<Boolean> =
+        userRepository.selectedUserInfo.flatMapLatest { userInfo ->
+            areBiometricsEnabledForDeviceEntryFromUserSetting.map {
+                if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+                    faceEnabledForUser[userInfo.id] ?: false
+                } else {
+                    biometricsEnabledForUser[userInfo.id] ?: false
+                }
             }
         }
 
@@ -264,31 +284,44 @@
             .distinctUntilChanged()
 
     private val isFaceAuthenticationEnabled: Flow<Boolean> =
-        combine(areBiometricsEnabledForCurrentUser, isFaceEnabledByDevicePolicy) {
+        combine(isFaceEnabledForCurrentUser, isFaceEnabledByDevicePolicy) {
             biometricsManagerSetting,
             devicePolicySetting ->
             biometricsManagerSetting && devicePolicySetting
         }
 
-    private val areBiometricsEnabledForDeviceEntryFromUserSetting: Flow<Pair<Int, Boolean>> =
+    private val areBiometricsEnabledForDeviceEntryFromUserSetting: Flow<Triple<Int, Boolean, Int>> =
         conflatedCallbackFlow {
                 val callback =
                     object : IBiometricEnabledOnKeyguardCallback.Stub() {
-                        override fun onChanged(enabled: Boolean, userId: Int) {
+                        override fun onChanged(enabled: Boolean, userId: Int, modality: Int) {
                             trySendWithFailureLogging(
-                                Pair(userId, enabled),
+                                Triple(userId, enabled, modality),
                                 TAG,
-                                "biometricsEnabled state changed"
+                                "biometricsEnabled state changed",
                             )
                         }
                     }
                 biometricManager?.registerEnabledOnKeyguardCallback(callback)
                 awaitClose {}
             }
-            .onEach { biometricsEnabledForUser[it.first] = it.second }
+            .onEach {
+                if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+                    when (it.third) {
+                        TYPE_FACE -> {
+                            faceEnabledForUser[it.first] = it.second
+                        }
+                        TYPE_FINGERPRINT -> {
+                            fingerprintEnabledForUser[it.first] = it.second
+                        }
+                    }
+                } else {
+                    biometricsEnabledForUser[it.first] = it.second
+                }
+            }
             // This is because the callback is binder-based and we want to avoid multiple callbacks
             // being registered.
-            .stateIn(scope, SharingStarted.Eagerly, Pair(0, false))
+            .stateIn(scope, SharingStarted.Eagerly, Triple(0, false, TYPE_NONE))
 
     private val isStrongBiometricAllowed: StateFlow<Boolean> =
         strongAuthTracker.isStrongBiometricAllowed.stateIn(
@@ -333,7 +366,7 @@
 
     override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
         isFingerprintEnrolled
-            .and(areBiometricsEnabledForCurrentUser)
+            .and(isFingerprintEnabledForCurrentUser)
             .and(isFingerprintEnabledByDevicePolicy)
             .stateIn(scope, SharingStarted.Eagerly, false)
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index d94424c..e19d6e9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -57,6 +57,7 @@
             // activity and a null second task, so the foreground task will be index 1, but when
             // opening the app selector in split screen mode, the foreground task will be the second
             // task in index 0.
+            // TODO(346588978): This needs to be updated for mixed groups
             val foregroundGroup =
                 if (groupedTasks.firstOrNull()?.splitBounds != null) groupedTasks.first()
                 else groupedTasks.elementAtOrNull(1)
@@ -69,7 +70,7 @@
                         it.taskInfo1,
                         it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible,
                         userManager.getUserInfo(it.taskInfo1.userId).toUserType(),
-                        it.splitBounds
+                        it.splitBounds,
                     )
 
                 val task2 =
@@ -78,7 +79,7 @@
                             it.taskInfo2!!,
                             it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible,
                             userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(),
-                            it.splitBounds
+                            it.splitBounds,
                         )
                     } else null
 
@@ -92,7 +93,7 @@
                 Integer.MAX_VALUE,
                 RECENT_IGNORE_UNAVAILABLE,
                 userTracker.userId,
-                backgroundExecutor
+                backgroundExecutor,
             ) { tasks ->
                 continuation.resume(tasks)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index b53685e..6d90784 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -57,7 +57,6 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
@@ -95,6 +94,7 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.transitions
 import com.android.compose.modifiers.height
 import com.android.compose.modifiers.padding
@@ -313,8 +313,8 @@
      */
     @Composable
     private fun CollapsableQuickSettingsSTL() {
-        val sceneState = remember {
-            MutableSceneTransitionLayoutState(
+        val sceneState =
+            rememberMutableSceneTransitionLayoutState(
                 viewModel.expansionState.toIdleSceneKey(),
                 transitions =
                     transitions {
@@ -323,7 +323,6 @@
                         }
                     },
             )
-        }
 
         LaunchedEffect(Unit) {
             synchronizeQsState(
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index c1e8032..40e6d28 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.classifier.Classifier
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -41,6 +42,7 @@
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -62,6 +64,8 @@
     private val splitEdgeDetector: SplitEdgeDetector,
     private val logger: SceneLogger,
     hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
+    val lightRevealScrim: LightRevealScrimViewModel,
+    val wallpaperViewModel: WallpaperViewModel,
     @Assisted view: View,
     @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
 ) : ExclusiveActivatable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 2885ce8..a56624c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -84,7 +84,7 @@
                 scrim.width * initialWidthMultiplier + -scrim.width * ovalWidthIncreaseAmount,
                 scrim.height * OVAL_INITIAL_TOP_PERCENT - scrim.height * interpolatedAmount,
                 scrim.width * (1f - initialWidthMultiplier) + scrim.width * ovalWidthIncreaseAmount,
-                scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount
+                scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount,
             )
         }
     }
@@ -98,7 +98,7 @@
             /* controlX1= */ 0.4f,
             /* controlY1= */ 0f,
             /* controlX2= */ 0.2f,
-            /* controlY2= */ 1f
+            /* controlY2= */ 1f,
         )
 
     override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
@@ -109,14 +109,14 @@
         scrim.startColorAlpha =
             getPercentPastThreshold(
                 1 - interpolatedAmount,
-                threshold = 1 - START_COLOR_REVEAL_PERCENTAGE
+                threshold = 1 - START_COLOR_REVEAL_PERCENTAGE,
             )
 
         scrim.revealGradientEndColorAlpha =
             1f -
                 getPercentPastThreshold(
                     interpolatedAmount,
-                    threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+                    threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE,
                 )
 
         // Start changing gradient bounds later to avoid harsh gradient in the beginning
@@ -127,14 +127,14 @@
                 left = scrim.viewWidth / 2 - (scrim.viewWidth / 2) * gradientBoundsAmount,
                 top = 0f,
                 right = scrim.viewWidth / 2 + (scrim.viewWidth / 2) * gradientBoundsAmount,
-                bottom = scrim.viewHeight.toFloat()
+                bottom = scrim.viewHeight.toFloat(),
             )
         } else {
             scrim.setRevealGradientBounds(
                 left = 0f,
                 top = scrim.viewHeight / 2 - (scrim.viewHeight / 2) * gradientBoundsAmount,
                 right = scrim.viewWidth.toFloat(),
-                bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount
+                bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount,
             )
         }
     }
@@ -166,7 +166,7 @@
             1f -
                 getPercentPastThreshold(
                     amount,
-                    threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+                    threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE,
                 )
 
         val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1f, amount)
@@ -175,14 +175,14 @@
                 left = -(scrim.viewWidth) * gradientBoundsAmount,
                 top = -(scrim.viewHeight) * gradientBoundsAmount,
                 right = (scrim.viewWidth) * gradientBoundsAmount,
-                bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount
+                bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount,
             )
         } else {
             scrim.setRevealGradientBounds(
                 left = -(scrim.viewWidth) * gradientBoundsAmount,
                 top = -(scrim.viewHeight) * gradientBoundsAmount,
                 right = (scrim.viewWidth) + (scrim.viewWidth) * gradientBoundsAmount,
-                bottom = (scrim.viewHeight) * gradientBoundsAmount
+                bottom = (scrim.viewHeight) * gradientBoundsAmount,
             )
         }
     }
@@ -212,7 +212,7 @@
     /** Radius of initial state of circle reveal */
     val startRadius: Int,
     /** Radius of end state of circle reveal */
-    val endRadius: Int
+    val endRadius: Int,
 ) : LightRevealEffect {
     override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
         // reveal amount updates already have an interpolator, so we intentionally use the
@@ -225,7 +225,7 @@
             centerX - radius /* left */,
             centerY - radius /* top */,
             centerX + radius /* right */,
-            centerY + radius /* bottom */
+            centerY + radius, /* bottom */
         )
     }
 }
@@ -259,7 +259,7 @@
                     powerButtonY - height * interpolatedAmount,
                     width * (1f + OFF_SCREEN_START_AMOUNT) +
                         width * INCREASE_MULTIPLIER * interpolatedAmount,
-                    powerButtonY + height * interpolatedAmount
+                    powerButtonY + height * interpolatedAmount,
                 )
             } else if (rotation == RotationUtils.ROTATION_LANDSCAPE) {
                 setRevealGradientBounds(
@@ -268,7 +268,7 @@
                         height * INCREASE_MULTIPLIER * interpolatedAmount,
                     powerButtonY + width * interpolatedAmount,
                     (-height * OFF_SCREEN_START_AMOUNT) +
-                        height * INCREASE_MULTIPLIER * interpolatedAmount
+                        height * INCREASE_MULTIPLIER * interpolatedAmount,
                 )
             } else {
                 // RotationUtils.ROTATION_SEASCAPE
@@ -278,7 +278,7 @@
                         height * INCREASE_MULTIPLIER * interpolatedAmount,
                     (width - powerButtonY) + width * interpolatedAmount,
                     height * (1f + OFF_SCREEN_START_AMOUNT) +
-                        height * INCREASE_MULTIPLIER * interpolatedAmount
+                        height * INCREASE_MULTIPLIER * interpolatedAmount,
                 )
             }
         }
@@ -296,9 +296,9 @@
 @JvmOverloads
 constructor(
     context: Context?,
-    attrs: AttributeSet?,
+    attrs: AttributeSet? = null,
     initialWidth: Int? = null,
-    initialHeight: Int? = null
+    initialHeight: Int? = null,
 ) : View(context, attrs) {
 
     private val logString = this::class.simpleName!! + "@" + hashCode()
@@ -322,8 +322,9 @@
                 revealEffect.setRevealAmountOnScrim(value, this)
                 updateScrimOpaque()
                 TrackTracer.instantForGroup(
-                    "scrim", { "light_reveal_amount $logString" },
-                    (field * 100).toInt()
+                    "scrim",
+                    { "light_reveal_amount $logString" },
+                    (field * 100).toInt(),
                 )
                 invalidate()
             }
@@ -440,7 +441,7 @@
                     1f,
                     intArrayOf(Color.TRANSPARENT, Color.WHITE),
                     floatArrayOf(0f, 1f),
-                    Shader.TileMode.CLAMP
+                    Shader.TileMode.CLAMP,
                 )
 
             // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
@@ -466,6 +467,7 @@
         viewWidth = measuredWidth
         viewHeight = measuredHeight
     }
+
     /**
      * Sets bounds for the transparent oval gradient that reveals the views below the scrim. This is
      * simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
@@ -513,7 +515,7 @@
         gradientPaint.colorFilter =
             PorterDuffColorFilter(
                 getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
-                PorterDuff.Mode.MULTIPLY
+                PorterDuff.Mode.MULTIPLY,
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 99e8472..541a07c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.chips.call.ui.viewmodel
 
 import android.view.View
-import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.Cuj
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
@@ -128,7 +128,7 @@
                 state.intent,
                 ActivityTransitionAnimator.Controller.fromView(
                     backgroundView,
-                    InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+                    Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
                 ),
             )
         }
@@ -145,7 +145,7 @@
                     StatusBarChipsModernization.assertInNewMode()
                     val animationController =
                         expandable.activityTransitionController(
-                            InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP
+                            Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP
                         )
                     activityStarter.postStartActivityDismissingKeyguard(
                         state.intent,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d43fed0..2cd8eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -31,7 +31,6 @@
 import android.annotation.IntDef;
 import android.graphics.Color;
 import android.os.Handler;
-import android.os.Trace;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Pair;
@@ -53,6 +52,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dagger.SysUISingleton;
@@ -82,6 +82,9 @@
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -90,9 +93,6 @@
 
 import javax.inject.Inject;
 
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
-
 /**
  * Controls both the scrim behind the notifications and in front of the notifications (when a
  * security method gets shown).
@@ -226,6 +226,8 @@
     private float mAdditionalScrimBehindAlphaKeyguard = 0f;
     // Combined scrim behind keyguard alpha of default scrim + additional scrim
     private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
+
+    static final float TRANSPARENT_BOUNCER_SCRIM_ALPHA = 0.54f;
     private final float mDefaultScrimAlpha;
 
     private float mRawPanelExpansionFraction;
@@ -340,7 +342,10 @@
             LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
         mScrimStateListener = lightBarController::setScrimState;
         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
-        mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
+        // All scrims default alpha need to match bouncer background alpha to make sure the
+        // transitions involving the bouncer are smooth and don't overshoot the bouncer alpha.
+        mDefaultScrimAlpha =
+                Flags.bouncerUiRevamp() ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : BUSY_SCRIM_ALPHA;
 
         mKeyguardStateController = keyguardStateController;
         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -974,8 +979,6 @@
                         mBehindTint,
                         interpolatedFraction);
             }
-        } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
-            mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
                 || mState == ScrimState.PULSING || mState == ScrimState.GLANCEABLE_HUB) {
             Pair<Integer, Float> result = calculateBackStateForState(mState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 8dcb663..1493729 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -16,17 +16,24 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.phone.ScrimController.BUSY_SCRIM_ALPHA;
+import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+
 import android.graphics.Color;
 
 import com.android.app.tracing.coroutines.TrackTracer;
+import com.android.systemui.Flags;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.res.R;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
 /**
  * Possible states of the ScrimController state machine.
  */
+@ExperimentalCoroutinesApi
 public enum ScrimState {
 
     /**
@@ -92,40 +99,19 @@
         }
     },
 
-    AUTH_SCRIMMED_SHADE {
-        @Override
-        public void prepare(ScrimState previousState) {
-            // notif scrim alpha values are determined by ScrimController#applyState
-            // based on the shade expansion
-
-            mFrontTint = mBackgroundColor;
-            mFrontAlpha = .66f;
-
-            mBehindTint = mBackgroundColor;
-            mBehindAlpha = 1f;
-        }
-    },
-
-    AUTH_SCRIMMED {
-        @Override
-        public void prepare(ScrimState previousState) {
-            mNotifTint = previousState.mNotifTint;
-            mNotifAlpha = previousState.mNotifAlpha;
-
-            mBehindTint = previousState.mBehindTint;
-            mBehindAlpha = previousState.mBehindAlpha;
-
-            mFrontTint = mBackgroundColor;
-            mFrontAlpha = .66f;
-        }
-    },
-
     /**
      * Showing password challenge on the keyguard.
      */
     BOUNCER {
         @Override
         public void prepare(ScrimState previousState) {
+            if (Flags.bouncerUiRevamp()) {
+                mBehindAlpha = mClipQsScrim ? 0.0f : TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+                mNotifAlpha = mClipQsScrim ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : 0;
+                mBehindTint = mNotifTint = mSurfaceColor;
+                mFrontAlpha = 0f;
+                return;
+            }
             mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
             mBehindTint = mClipQsScrim ? mBackgroundColor : mSurfaceColor;
             mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
@@ -136,6 +122,10 @@
         @Override
         public void setSurfaceColor(int surfaceColor) {
             super.setSurfaceColor(surfaceColor);
+            if (Flags.bouncerUiRevamp()) {
+                mBehindTint = mNotifTint = mSurfaceColor;
+                return;
+            }
             if (!mClipQsScrim) {
                 mBehindTint = mSurfaceColor;
             }
@@ -146,15 +136,38 @@
      * Showing password challenge on top of a FLAG_SHOW_WHEN_LOCKED activity.
      */
     BOUNCER_SCRIMMED {
+        @ExperimentalCoroutinesApi
         @Override
         public void prepare(ScrimState previousState) {
+            if (Flags.bouncerUiRevamp()) {
+                mBehindAlpha = 0f;
+                mFrontAlpha = TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+                mFrontTint = mSurfaceColor;
+                return;
+            }
             mBehindAlpha = 0;
             mFrontAlpha = mDefaultScrimAlpha;
         }
+
+        @Override
+        public boolean shouldBlendWithMainColor() {
+            return !Flags.bouncerUiRevamp();
+        }
     },
 
     SHADE_LOCKED {
         @Override
+        public void setDefaultScrimAlpha(float defaultScrimAlpha) {
+            super.setDefaultScrimAlpha(defaultScrimAlpha);
+            if (!Flags.notificationShadeBlur()) {
+                // Temporary change that prevents the shade from being semi-transparent when
+                // bouncer blur is enabled but notification shade blur is not enabled. This is
+                // required to perf test these two flags independently.
+                mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
+            }
+        }
+
+        @Override
         public void prepare(ScrimState previousState) {
             mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
             mNotifAlpha = 1f;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index bac2c47..1729a4d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -55,6 +55,7 @@
 import com.android.systemui.plugins.clocks.ThemeConfig
 import com.android.systemui.plugins.clocks.ZenData
 import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.BatteryController
@@ -131,6 +132,7 @@
     @Mock private lateinit var parentView: View
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var powerInteractor: PowerInteractor
 
     @Mock private lateinit var zenModeController: ZenModeController
     private var zenModeControllerCallback: ZenModeController.Callback? = null
@@ -178,6 +180,7 @@
                 zenModeController,
                 zenModeInteractor,
                 userTracker,
+                powerInteractor,
             )
         underTest.clock = clock
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 839a2bd..2645811 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -18,6 +18,8 @@
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_ANY_BIOMETRIC;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
@@ -1507,6 +1509,72 @@
     }
 
     @Test
+    @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void listenForFingerprint_whenEnabledForUser_typeFingerprint()
+            throws RemoteException {
+        // GIVEN keyguard showing
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+
+        biometricsEnabledForCurrentUser(true, TYPE_FINGERPRINT);
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
+    }
+
+    @Test
+    @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void doNotListenForFingerprint_whenDisabledForUser_typeFingerprint()
+            throws RemoteException {
+        // GIVEN keyguard showing
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+
+        biometricsEnabledForCurrentUser(false, TYPE_FINGERPRINT);
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
+    }
+
+    @Test
+    @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void listenForFingerprint_typeFingerprintEnabled_typeFaceDisabled()
+            throws RemoteException {
+        // GIVEN keyguard showing
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+
+        // Enable with fingerprint
+        biometricsEnabledForCurrentUser(true, TYPE_FINGERPRINT);
+        mTestableLooper.processAllMessages();
+
+        // Disable with face
+        biometricsEnabledForCurrentUser(false, TYPE_FACE);
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
+    }
+
+    @Test
+    @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void doNotListenForFingerprint_typeFingerprintDisabled_typeFaceEnabled()
+            throws RemoteException {
+        // GIVEN keyguard showing
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+
+        // Enable with face
+        biometricsEnabledForCurrentUser(true, TYPE_FACE);
+        mTestableLooper.processAllMessages();
+
+        // Disable with fingerprint
+        biometricsEnabledForCurrentUser(false, TYPE_FINGERPRINT);
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
+    }
+
+    @Test
     public void testOccludingAppFingerprintListeningState() {
         // GIVEN keyguard isn't visible (app occluding)
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -2464,8 +2532,13 @@
     }
 
     private void biometricsEnabledForCurrentUser() throws RemoteException {
-        mBiometricEnabledOnKeyguardCallback.onChanged(true,
-                mSelectedUserInteractor.getSelectedUserId());
+        biometricsEnabledForCurrentUser(true /* enabled */, TYPE_FINGERPRINT);
+    }
+
+    private void biometricsEnabledForCurrentUser(boolean enabled, int modality)
+            throws RemoteException {
+        mBiometricEnabledOnKeyguardCallback.onChanged(enabled,
+                mSelectedUserInteractor.getSelectedUserId(), modality);
     }
 
     private void primaryAuthNotRequiredByStrongAuthTracker() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 38ddb3e..a02d333 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -30,7 +30,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -1425,8 +1424,7 @@
         HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList(
                 ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, BOUNCER,
                 ScrimState.DREAMING, ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR,
-                ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.AUTH_SCRIMMED,
-                ScrimState.AUTH_SCRIMMED_SHADE, ScrimState.GLANCEABLE_HUB,
+                ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.GLANCEABLE_HUB,
                 ScrimState.GLANCEABLE_HUB_OVER_DREAM));
 
         for (ScrimState state : ScrimState.values()) {
@@ -1451,79 +1449,6 @@
     }
 
     @Test
-    public void testAuthScrim_setClipQSScrimTrue_notifScrimOpaque_whenShadeFullyExpanded() {
-        // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
-        // with the camera app occluding the keyguard)
-        mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
-        mScrimController.setClipsQsScrim(true);
-        mScrimController.setRawPanelExpansionFraction(1);
-        // notifications scrim alpha change require calling setQsPosition
-        mScrimController.setQsPosition(0, 300);
-        finishAnimationsImmediately();
-
-        // WHEN the user triggers the auth bouncer
-        mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
-        finishAnimationsImmediately();
-
-        assertEquals("Behind scrim should be opaque",
-                mScrimBehind.getViewAlpha(), 1, 0.0);
-        assertEquals("Notifications scrim should be opaque",
-                mNotificationsScrim.getViewAlpha(), 1, 0.0);
-
-        assertScrimTinted(Map.of(
-                mScrimInFront, true,
-                mScrimBehind, true,
-                mNotificationsScrim, false
-        ));
-    }
-
-
-    @Test
-    public void testAuthScrim_setClipQSScrimFalse_notifScrimOpaque_whenShadeFullyExpanded() {
-        // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
-        // with the camera app occluding the keyguard)
-        mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
-        mScrimController.setClipsQsScrim(false);
-        mScrimController.setRawPanelExpansionFraction(1);
-        // notifications scrim alpha change require calling setQsPosition
-        mScrimController.setQsPosition(0, 300);
-        finishAnimationsImmediately();
-
-        // WHEN the user triggers the auth bouncer
-        mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
-        finishAnimationsImmediately();
-
-        assertEquals("Behind scrim should be opaque",
-                mScrimBehind.getViewAlpha(), 1, 0.0);
-        assertEquals("Notifications scrim should be opaque",
-                mNotificationsScrim.getViewAlpha(), 1, 0.0);
-
-        assertScrimTinted(Map.of(
-                mScrimInFront, true,
-                mScrimBehind, true,
-                mNotificationsScrim, false
-        ));
-    }
-
-    @Test
-    public void testAuthScrimKeyguard() {
-        // GIVEN device is on the keyguard
-        mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
-        finishAnimationsImmediately();
-
-        // WHEN the user triggers the auth bouncer
-        mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED);
-        finishAnimationsImmediately();
-
-        // THEN the front scrim is updated and the KEYGUARD scrims are the same as the
-        // KEYGUARD scrim state
-        assertScrimAlpha(Map.of(
-                mScrimInFront, SEMI_TRANSPARENT,
-                mScrimBehind, SEMI_TRANSPARENT,
-                mNotificationsScrim, TRANSPARENT));
-    }
-
-    @Test
     public void testScrimsVisible_whenShadeVisible() {
         mScrimController.setClipsQsScrim(true);
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt
new file mode 100644
index 0000000..93da1f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.lightRevealScrimInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.lightRevealScrimViewModel by Fixture {
+    LightRevealScrimViewModel(interactor = lightRevealScrimInteractor)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 0eca818..609f97d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -4,6 +4,7 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.haptics.msdl.msdlPlayer
+import com.android.systemui.keyguard.ui.viewmodel.lightRevealScrimViewModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.power.domain.interactor.powerInteractor
@@ -19,6 +20,7 @@
 import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
+import com.android.systemui.wallpapers.ui.viewmodel.wallpaperViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import org.mockito.kotlin.mock
 
@@ -96,6 +98,8 @@
                 hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
                 view = view,
                 motionEventHandlerReceiver = motionEventHandlerReceiver,
+                lightRevealScrim = lightRevealScrimViewModel,
+                wallpaperViewModel = wallpaperViewModel,
             )
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
index 1d8c891..ddb9a3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
@@ -26,13 +26,11 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.user.data.repository.userRepository
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-@OptIn(ExperimentalCoroutinesApi::class)
 val Kosmos.wallpaperRepository by Fixture {
     WallpaperRepositoryImpl(
         context = applicationContext,
-        scope = testScope,
+        scope = testScope.backgroundScope,
         bgDispatcher = testDispatcher,
         broadcastDispatcher = broadcastDispatcher,
         userRepository = userRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt
new file mode 100644
index 0000000..bb6596e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 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.wallpapers.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.wallpapers.domain.interactor.wallpaperInteractor
+
+val Kosmos.wallpaperViewModel by Fixture { WallpaperViewModel(interactor = wallpaperInteractor) }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index cffdfbd..8ef44ad 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -5666,6 +5666,16 @@
         }
 
         public boolean canAccessAppWidget(Widget widget, int uid, String packageName) {
+            if (packageName != null
+                    && widget.provider != null
+                    && isDifferentPackageFromProvider(widget.provider, packageName)
+                    && widget.host != null
+                    && isDifferentPackageFromHost(widget.host, packageName)) {
+                // An AppWidget can only be accessed by either
+                // 1. The package that provides the AppWidget.
+                // 2. The package that hosts the AppWidget.
+                return false;
+            }
             if (isHostInPackageForUid(widget.host, uid, packageName)) {
                 // Apps hosting the AppWidget have access to it.
                 return true;
@@ -5768,6 +5778,22 @@
                     && provider.id.componentName.getPackageName().equals(packageName);
         }
 
+        private boolean isDifferentPackageFromHost(
+                @NonNull final Host host, @NonNull final String packageName) {
+            if (host.id == null || host.id.packageName == null) {
+                return true;
+            }
+            return !packageName.equals(host.id.packageName);
+        }
+
+        private boolean isDifferentPackageFromProvider(
+                @NonNull final Provider provider, @NonNull final String packageName) {
+            if (provider.id == null || provider.id.componentName == null) {
+                return true;
+            }
+            return !packageName.equals(provider.id.componentName.getPackageName());
+        }
+
         private boolean isProfileEnabled(int profileId) {
             final long identity = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 42385fc..bb49337 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -230,6 +230,7 @@
         "notification_flags_lib",
         "power_hint_flags_lib",
         "biometrics_flags_lib",
+        "aconfig_settings_flags_lib",
         "am_flags_lib",
         "com_android_server_accessibility_flags_lib",
         "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index f58e3f8..23edafc 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -78,6 +78,7 @@
      * This is for verifying the UID report flow.
      */
     private static final boolean VALIDATE_UID_STATES = true;
+    @GuardedBy("mLock")
     private final ActiveUids mValidateUids;
 
     UidObserverController(@NonNull Handler handler) {
@@ -285,31 +286,30 @@
         }
         mUidObservers.finishBroadcast();
 
-        if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
-            for (int j = 0; j < numUidChanges; ++j) {
-                final ChangeRecord item = mActiveUidChanges[j];
-                if ((item.change & UidRecord.CHANGE_GONE) != 0) {
-                    mValidateUids.remove(item.uid);
-                } else {
-                    UidRecord validateUid = mValidateUids.get(item.uid);
-                    if (validateUid == null) {
-                        validateUid = new UidRecord(item.uid, null);
-                        mValidateUids.put(item.uid, validateUid);
+        synchronized (mLock) {
+            if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
+                for (int j = 0; j < numUidChanges; ++j) {
+                    final ChangeRecord item = mActiveUidChanges[j];
+                    if ((item.change & UidRecord.CHANGE_GONE) != 0) {
+                        mValidateUids.remove(item.uid);
+                    } else {
+                        UidRecord validateUid = mValidateUids.get(item.uid);
+                        if (validateUid == null) {
+                            validateUid = new UidRecord(item.uid, null);
+                            mValidateUids.put(item.uid, validateUid);
+                        }
+                        if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
+                            validateUid.setIdle(true);
+                        } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
+                            validateUid.setIdle(false);
+                        }
+                        validateUid.setSetProcState(item.procState);
+                        validateUid.setCurProcState(item.procState);
+                        validateUid.setSetCapability(item.capability);
+                        validateUid.setCurCapability(item.capability);
                     }
-                    if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
-                        validateUid.setIdle(true);
-                    } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
-                        validateUid.setIdle(false);
-                    }
-                    validateUid.setSetProcState(item.procState);
-                    validateUid.setCurProcState(item.procState);
-                    validateUid.setSetCapability(item.capability);
-                    validateUid.setCurCapability(item.capability);
                 }
             }
-        }
-
-        synchronized (mLock) {
             for (int j = 0; j < numUidChanges; j++) {
                 final ChangeRecord changeRecord = mActiveUidChanges[j];
                 changeRecord.isPending = false;
@@ -436,7 +436,9 @@
     }
 
     UidRecord getValidateUidRecord(int uid) {
-        return mValidateUids.get(uid);
+        synchronized (mLock) {
+            return mValidateUids.get(uid);
+        }
     }
 
     void dump(@NonNull PrintWriter pw, @Nullable String dumpPackage) {
@@ -491,12 +493,16 @@
 
     boolean dumpValidateUids(@NonNull PrintWriter pw, @Nullable String dumpPackage, int dumpAppId,
             @NonNull String header, boolean needSep) {
-        return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
+        synchronized (mLock) {
+            return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
+        }
     }
 
     void dumpValidateUidsProto(@NonNull ProtoOutputStream proto, @Nullable String dumpPackage,
             int dumpAppId, long fieldId) {
-        mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
+        synchronized (mLock) {
+            mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
+        }
     }
 
     static final class ChangeRecord {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d2073aa..8e09e3b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -33,6 +33,7 @@
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
@@ -3267,6 +3268,11 @@
                         packageName);
             }
             if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+                // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+                if (code == OP_BLUETOOTH_CONNECT) {
+                    Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as incoming "
+                            + "package: " + packageName + " and uid: " + uid + " is invalid");
+                }
                 return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                         packageName);
             }
@@ -3306,6 +3312,13 @@
             }
         } catch (SecurityException e) {
             logVerifyAndGetBypassFailure(uid, e, "noteOperation");
+            // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+            if (code == OP_BLUETOOTH_CONNECT) {
+                Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                        + " verifyAndGetBypass returned a SecurityException for package: "
+                        + packageName + " and uid: " + uid + " and attributionTag: "
+                        + attributionTag, e);
+            }
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -3323,6 +3336,17 @@
                 if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
                         + " package " + packageName + "flags: " +
                         AppOpsManager.flagsToString(flags));
+                // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+                if (code == OP_BLUETOOTH_CONNECT) {
+                    Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                            + " #getOpsLocked returned null for"
+                            + " uid: " + uid
+                            + " packageName: " + packageName
+                            + " attributionTag: " + attributionTag
+                            + " pvr.isAttributionTagValid: " + pvr.isAttributionTagValid
+                            + " pvr.bypass: " + pvr.bypass);
+                    Slog.e(TAG, "mUidStates.get(" + uid + "): " + mUidStates.get(uid));
+                }
                 return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                         packageName);
             }
@@ -3367,6 +3391,11 @@
                     attributedOp.rejected(uidState.getState(), flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
                             virtualDeviceId, flags, uidMode);
+                    // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+                    if (code == OP_BLUETOOTH_CONNECT && uidMode == MODE_ERRORED) {
+                        Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                                + " uid mode is MODE_ERRORED");
+                    }
                     return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
                 }
             } else {
@@ -3386,6 +3415,11 @@
                     attributedOp.rejected(uidState.getState(), flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
                             virtualDeviceId, flags, mode);
+                    // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+                    if (code == OP_BLUETOOTH_CONNECT && mode == MODE_ERRORED) {
+                        Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                                + " package mode is MODE_ERRORED");
+                    }
                     return new SyncNotedAppOp(mode, code, attributionTag, packageName);
                 }
             }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c125d2d..12b666f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11953,7 +11953,7 @@
         mLoudnessCodecHelper.startLoudnessCodecUpdates(sessionId);
     }
 
-    /** @see LoudnessCodecController#release() */
+    /** @see LoudnessCodecController#close() */
     @Override
     public void stopLoudnessCodecUpdates(int sessionId) {
         mLoudnessCodecHelper.stopLoudnessCodecUpdates(sessionId);
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index b365ef7..15b1f22 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics;
 
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_ANY_BIOMETRIC;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
@@ -81,6 +82,7 @@
 import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
@@ -100,8 +102,8 @@
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Supplier;
 
 /**
@@ -263,6 +265,14 @@
                 Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED);
         private final Uri BIOMETRIC_APP_ENABLED =
                 Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_APP_ENABLED);
+        private final Uri FACE_KEYGUARD_ENABLED =
+                Settings.Secure.getUriFor(Settings.Secure.FACE_KEYGUARD_ENABLED);
+        private final Uri FACE_APP_ENABLED =
+                Settings.Secure.getUriFor(Settings.Secure.FACE_APP_ENABLED);
+        private final Uri FINGERPRINT_KEYGUARD_ENABLED =
+                Settings.Secure.getUriFor(Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED);
+        private final Uri FINGERPRINT_APP_ENABLED =
+                Settings.Secure.getUriFor(Settings.Secure.FINGERPRINT_APP_ENABLED);
         private final Uri MANDATORY_BIOMETRICS_ENABLED =
                 Settings.Secure.getUriFor(Settings.Secure.MANDATORY_BIOMETRICS);
         private final Uri MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED = Settings.Secure.getUriFor(
@@ -274,6 +284,10 @@
 
         private final Map<Integer, Boolean> mBiometricEnabledOnKeyguard = new HashMap<>();
         private final Map<Integer, Boolean> mBiometricEnabledForApps = new HashMap<>();
+        private final SparseBooleanArray mFaceEnabledOnKeyguard = new SparseBooleanArray();
+        private final SparseBooleanArray mFaceEnabledForApps = new SparseBooleanArray();
+        private final SparseBooleanArray mFingerprintEnabledOnKeyguard = new SparseBooleanArray();
+        private final SparseBooleanArray mFingerprintEnabledForApps = new SparseBooleanArray();
         private final Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>();
         private final Map<Integer, Boolean> mMandatoryBiometricsEnabled = new HashMap<>();
         private final Map<Integer, Boolean> mMandatoryBiometricsRequirementsSatisfied =
@@ -323,6 +337,23 @@
                         false /* notifyForDescendants */,
                         this /* observer */,
                         UserHandle.USER_ALL);
+            } else if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+                mContentResolver.registerContentObserver(FINGERPRINT_KEYGUARD_ENABLED,
+                        false /* notifyForDescendants */,
+                        this /* observer */,
+                        UserHandle.USER_ALL);
+                mContentResolver.registerContentObserver(FACE_KEYGUARD_ENABLED,
+                        false /* notifyForDescendants */,
+                        this /* observer */,
+                        UserHandle.USER_ALL);
+                mContentResolver.registerContentObserver(FINGERPRINT_APP_ENABLED,
+                        false /* notifyForDescendants */,
+                        this /* observer */,
+                        UserHandle.USER_ALL);
+                mContentResolver.registerContentObserver(FACE_APP_ENABLED,
+                        false /* notifyForDescendants */,
+                        this /* observer */,
+                        UserHandle.USER_ALL);
             } else {
                 mContentResolver.registerContentObserver(BIOMETRIC_KEYGUARD_ENABLED,
                         false /* notifyForDescendants */,
@@ -357,7 +388,7 @@
                                 userId) != 0);
 
                 if (userId == ActivityManager.getCurrentUser() && !selfChange) {
-                    notifyEnabledOnKeyguardCallbacks(userId);
+                    notifyEnabledOnKeyguardCallbacks(userId, TYPE_FACE);
                 }
             } else if (FACE_UNLOCK_APP_ENABLED.equals(uri)) {
                 mBiometricEnabledForApps.put(userId, Settings.Secure.getIntForUser(
@@ -379,7 +410,27 @@
                         userId) != 0);
 
                 if (userId == ActivityManager.getCurrentUser() && !selfChange) {
-                    notifyEnabledOnKeyguardCallbacks(userId);
+                    notifyEnabledOnKeyguardCallbacks(userId, TYPE_ANY_BIOMETRIC);
+                }
+            } else if (FACE_KEYGUARD_ENABLED.equals(uri)) {
+                mFaceEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
+                        mContentResolver,
+                        Settings.Secure.FACE_KEYGUARD_ENABLED,
+                        DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */,
+                        userId) != 0);
+
+                if (userId == ActivityManager.getCurrentUser() && !selfChange) {
+                    notifyEnabledOnKeyguardCallbacks(userId, TYPE_FACE);
+                }
+            }  else if (FINGERPRINT_KEYGUARD_ENABLED.equals(uri)) {
+                mFingerprintEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
+                        mContentResolver,
+                        Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED,
+                        DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */,
+                        userId) != 0);
+
+                if (userId == ActivityManager.getCurrentUser() && !selfChange) {
+                    notifyEnabledOnKeyguardCallbacks(userId, TYPE_FINGERPRINT);
                 }
             } else if (BIOMETRIC_APP_ENABLED.equals(uri)) {
                 mBiometricEnabledForApps.put(userId, Settings.Secure.getIntForUser(
@@ -387,6 +438,18 @@
                         Settings.Secure.BIOMETRIC_APP_ENABLED,
                         DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
                         userId) != 0);
+            } else if (FACE_APP_ENABLED.equals(uri)) {
+                mFaceEnabledForApps.put(userId, Settings.Secure.getIntForUser(
+                        mContentResolver,
+                        Settings.Secure.FACE_APP_ENABLED,
+                        DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
+                        userId) != 0);
+            } else if (FINGERPRINT_APP_ENABLED.equals(uri)) {
+                mFingerprintEnabledForApps.put(userId, Settings.Secure.getIntForUser(
+                        mContentResolver,
+                        Settings.Secure.FINGERPRINT_APP_ENABLED,
+                        DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
+                        userId) != 0);
             } else if (MANDATORY_BIOMETRICS_ENABLED.equals(uri)) {
                 updateMandatoryBiometricsForAllProfiles(userId);
             } else if (MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED.equals(uri)) {
@@ -394,26 +457,60 @@
             }
         }
 
-        public boolean getEnabledOnKeyguard(int userId) {
-            if (!mBiometricEnabledOnKeyguard.containsKey(userId)) {
-                if (mUseLegacyFaceOnlySettings) {
-                    onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, userId);
-                } else {
-                    onChange(true /* selfChange */, BIOMETRIC_KEYGUARD_ENABLED, userId);
+        public boolean getEnabledOnKeyguard(int userId, int modality) {
+            if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+                if (modality == TYPE_FACE) {
+                    if (mFaceEnabledOnKeyguard.indexOfKey(userId) < 0) {
+                        onChange(true /* selfChange */, FACE_KEYGUARD_ENABLED, userId);
+                    }
+                    return mFaceEnabledOnKeyguard.get(userId, DEFAULT_KEYGUARD_ENABLED);
+                } else if (modality == TYPE_FINGERPRINT) {
+                    if (mFingerprintEnabledOnKeyguard.indexOfKey(userId) < 0) {
+                        onChange(true /* selfChange */, FINGERPRINT_KEYGUARD_ENABLED, userId);
+                    }
+                    return mFingerprintEnabledOnKeyguard.get(userId, DEFAULT_KEYGUARD_ENABLED);
+                } else { // modality == TYPE_ANY_BIOMETRIC
+                    return mFingerprintEnabledOnKeyguard.get(userId, DEFAULT_KEYGUARD_ENABLED)
+                            || mFaceEnabledOnKeyguard.get(userId, DEFAULT_KEYGUARD_ENABLED);
                 }
+            } else {
+                if (!mBiometricEnabledOnKeyguard.containsKey(userId)) {
+                    if (mUseLegacyFaceOnlySettings) {
+                        onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, userId);
+                    } else {
+                        onChange(true /* selfChange */, BIOMETRIC_KEYGUARD_ENABLED, userId);
+                    }
+                }
+                return mBiometricEnabledOnKeyguard.get(userId);
             }
-            return mBiometricEnabledOnKeyguard.get(userId);
         }
 
-        public boolean getEnabledForApps(int userId) {
-            if (!mBiometricEnabledForApps.containsKey(userId)) {
-                if (mUseLegacyFaceOnlySettings) {
-                    onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId);
-                } else {
-                    onChange(true /* selfChange */, BIOMETRIC_APP_ENABLED, userId);
+        public boolean getEnabledForApps(int userId, int modality) {
+            if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+                if (modality == TYPE_FACE) {
+                    if (mFaceEnabledForApps.indexOfKey(userId) < 0) {
+                        onChange(true /* selfChange */, FACE_APP_ENABLED, userId);
+                    }
+                    return mFaceEnabledForApps.get(userId, DEFAULT_APP_ENABLED);
+                } else if (modality == TYPE_FINGERPRINT) {
+                    if (mFingerprintEnabledForApps.indexOfKey(userId) < 0) {
+                        onChange(true /* selfChange */, FINGERPRINT_APP_ENABLED, userId);
+                    }
+                    return mFingerprintEnabledForApps.get(userId, DEFAULT_APP_ENABLED);
+                } else { // modality == TYPE_ANY_BIOMETRIC
+                    return mFingerprintEnabledForApps.get(userId, DEFAULT_APP_ENABLED)
+                            || mFaceEnabledForApps.get(userId, DEFAULT_APP_ENABLED);
                 }
+            } else {
+                if (!mBiometricEnabledForApps.containsKey(userId)) {
+                    if (mUseLegacyFaceOnlySettings) {
+                        onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId);
+                    } else {
+                        onChange(true /* selfChange */, BIOMETRIC_APP_ENABLED, userId);
+                    }
+                }
+                return mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED);
             }
-            return mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED);
         }
 
         public boolean getConfirmationAlwaysRequired(@BiometricAuthenticator.Modality int modality,
@@ -444,17 +541,16 @@
                     DEFAULT_MANDATORY_BIOMETRICS_STATUS)
                     && mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
                     DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
-                    && getEnabledForApps(userId)
+                    && getEnabledForApps(userId, TYPE_ANY_BIOMETRIC)
                     && (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
                     || mFaceEnrolledForUser.getOrDefault(userId, false /* default */));
         }
 
-        void notifyEnabledOnKeyguardCallbacks(int userId) {
+        void notifyEnabledOnKeyguardCallbacks(int userId, int modality) {
             List<EnabledOnKeyguardCallback> callbacks = mCallbacks;
+            final boolean enabled = getEnabledOnKeyguard(userId, modality);
             for (int i = 0; i < callbacks.size(); i++) {
-                callbacks.get(i).notify(
-                        mBiometricEnabledOnKeyguard.getOrDefault(userId, DEFAULT_KEYGUARD_ENABLED),
-                        userId);
+                callbacks.get(i).notify(enabled, userId, modality);
             }
         }
 
@@ -596,9 +692,9 @@
             }
         }
 
-        void notify(boolean enabled, int userId) {
+        void notify(boolean enabled, int userId, int modality) {
             try {
-                mCallback.onChanged(enabled, userId);
+                mCallback.onChanged(enabled, userId, modality);
             } catch (DeadObjectException e) {
                 Slog.w(TAG, "Death while invoking notify", e);
                 mEnabledOnKeyguardCallbacks.remove(this);
@@ -930,8 +1026,16 @@
             try {
                 for (UserInfo userInfo: aliveUsers) {
                     final int userId = userInfo.id;
-                    callback.onChanged(mSettingObserver.getEnabledOnKeyguard(userId),
-                            userId);
+                    if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+                        callback.onChanged(mSettingObserver.getEnabledOnKeyguard(userId, TYPE_FACE),
+                                userId, TYPE_FACE);
+                        callback.onChanged(
+                                mSettingObserver.getEnabledOnKeyguard(userId, TYPE_FINGERPRINT),
+                                userId, TYPE_FINGERPRINT);
+                    } else {
+                        callback.onChanged(mSettingObserver.getEnabledOnKeyguard(userId,
+                                        TYPE_ANY_BIOMETRIC), userId, TYPE_ANY_BIOMETRIC);
+                    }
                 }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception", e);
@@ -1309,7 +1413,15 @@
                         @Override
                         public void onUserSwitchComplete(int newUserId) {
                             mSettingObserver.updateContentObserver();
-                            mSettingObserver.notifyEnabledOnKeyguardCallbacks(newUserId);
+                            if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+                                mSettingObserver.notifyEnabledOnKeyguardCallbacks(newUserId,
+                                        TYPE_FACE);
+                                mSettingObserver.notifyEnabledOnKeyguardCallbacks(
+                                        newUserId, TYPE_FINGERPRINT);
+                            } else {
+                                mSettingObserver.notifyEnabledOnKeyguardCallbacks(
+                                        newUserId, TYPE_ANY_BIOMETRIC);
+                            }
                         }
                     }, BiometricService.class.getName()
             );
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 6ed1ac85..c739118 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -284,7 +284,7 @@
 
     private static boolean isEnabledForApp(BiometricService.SettingObserver settingObserver,
             @BiometricAuthenticator.Modality int modality, int userId) {
-        return settingObserver.getEnabledForApps(userId);
+        return settingObserver.getEnabledForApps(userId, modality);
     }
 
     private static boolean isBiometricDisabledByDevicePolicy(
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index e25ea4b..d1f07cb 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -90,6 +90,8 @@
                         (reason) -> updateTouchpadRightClickZoneEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_SYSTEM_GESTURES),
                         (reason) -> updateTouchpadSystemGesturesEnabled()),
+                Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_ACCELERATION_ENABLED),
+                        (reason) -> updateTouchpadAccelerationEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
                         (reason) -> updateShowTouches()),
                 Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
@@ -241,6 +243,11 @@
         mNative.setTouchpadSystemGesturesEnabled(InputSettings.useTouchpadSystemGestures(mContext));
     }
 
+    private void updateTouchpadAccelerationEnabled() {
+        mNative.setTouchpadAccelerationEnabled(
+                InputSettings.isTouchpadAccelerationEnabled(mContext));
+    }
+
     private void updateShowTouches() {
         mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
     }
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 4d38c84..f34338a 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -158,6 +158,8 @@
 
     void setTouchpadSystemGesturesEnabled(boolean enabled);
 
+    void setTouchpadAccelerationEnabled(boolean enabled);
+
     void setShowTouches(boolean enabled);
 
     void setNonInteractiveDisplays(int[] displayIds);
@@ -463,6 +465,9 @@
         public native void setTouchpadSystemGesturesEnabled(boolean enabled);
 
         @Override
+        public native void setTouchpadAccelerationEnabled(boolean enabled);
+
+        @Override
         public native void setShowTouches(boolean enabled);
 
         @Override
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f089c3a..b39b6fd 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
 import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
@@ -46,6 +47,7 @@
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.notification.Flags.preventZenDeviceEffectsWhileDriving;
 
 import static java.util.Objects.requireNonNull;
 
@@ -2379,6 +2381,26 @@
             }
 
             if (Flags.modesApi()) {
+                // Prevent other rules from applying grayscale if Driving is active (but allow it
+                // if _Driving itself_ wants grayscale).
+                if (Flags.modesUi() && preventZenDeviceEffectsWhileDriving()) {
+                    boolean hasActiveDriving = false;
+                    boolean hasActiveDrivingWithGrayscale = false;
+                    for (ZenRule rule : mConfig.automaticRules.values()) {
+                        if (rule.isActive() && rule.type == TYPE_DRIVING) {
+                            hasActiveDriving = true;
+                            if (rule.zenDeviceEffects != null
+                                    && rule.zenDeviceEffects.shouldDisplayGrayscale()) {
+                                hasActiveDrivingWithGrayscale = true;
+                                break; // Further rules won't affect decision.
+                            }
+                        }
+                    }
+                    if (hasActiveDriving && !hasActiveDrivingWithGrayscale) {
+                        deviceEffectsBuilder.setShouldDisplayGrayscale(false);
+                    }
+                }
+
                 ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
                 if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
                     mConsolidatedDeviceEffects = deviceEffects;
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index b4a8aee..822ff48 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -190,3 +190,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "prevent_zen_device_effects_while_driving"
+  namespace: "systemui"
+  description: "Don't apply certain device effects (such as grayscale) from active zen rules, if a rule of TYPE_DRIVING is active"
+  bug: "390389174"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 9d840d0..b905041 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -26,6 +26,7 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
 import static android.permission.flags.Flags.serverSideAttributionRegistration;
@@ -1668,7 +1669,22 @@
                         throw new SecurityException(msg + ":" + e.getMessage());
                     }
                 }
-                return Math.max(checkedOpResult, notedOpResult);
+                int result = Math.max(checkedOpResult, notedOpResult);
+                // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+                if (op == OP_BLUETOOTH_CONNECT && result == MODE_ERRORED) {
+                    if (result == checkedOpResult) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                + " checkOp for resolvedAttributionSource "
+                                + resolvedAttributionSource + " and op " + op
+                                + " returned MODE_ERRORED");
+                    } else {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                + " noteOp for resolvedAttributionSource "
+                                + resolvedAttributionSource + " and op " + notedOp
+                                + " returned MODE_ERRORED");
+                    }
+                }
+                return result;
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4300b60..f4870d5 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -406,7 +406,7 @@
 /**
  * An entry in the history task, representing an activity.
  */
-final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
+final class ActivityRecord extends WindowToken {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_ATM;
     private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
     private static final String TAG_APP = TAG + POSTFIX_APP;
@@ -736,9 +736,6 @@
      */
     boolean mAllowCrossUidActivitySwitchFromBelow;
 
-    /** Have we been asked to have this token keep the screen frozen? */
-    private boolean mFreezingScreen;
-
     // These are used for determining when all windows associated with
     // an activity have been drawn, so they can be made visible together
     // at the same time.
@@ -4511,8 +4508,6 @@
             removeIfPossible();
         }
 
-        stopFreezingScreen(true, true);
-
         final DisplayContent dc = getDisplayContent();
         if (dc.mFocusedApp == this) {
             ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
@@ -5793,9 +5788,7 @@
                         + " visibleRequested=%b, isInTransition=%b, runningAnimation=%b, caller=%s",
                 this, isVisible(), mVisibleRequested, isInTransition(), runningAnimation,
                 Debug.getCallers(5));
-        if (!visible) {
-            stopFreezingScreen(true, true);
-        } else {
+        if (visible) {
             // If we are being set visible, and the starting window is not yet displayed,
             // then make sure it doesn't get displayed.
             if (mStartingWindow != null && !mStartingWindow.isDrawn()
@@ -5803,9 +5796,6 @@
                 mStartingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
                 mStartingWindow.mLegacyPolicyVisibilityAfterAnim = false;
             }
-            // We are becoming visible, so better freeze the screen with the windows that are
-            // getting visible so we also wait for them.
-            forAllWindows(mWmService::makeWindowFreezingScreenIfNeededLocked, true);
         }
         // dispatchTaskInfoChangedIfNeeded() right after ActivityRecord#setVisibility() can report
         // the stale visible state, because the state will be updated after the app transition.
@@ -6842,123 +6832,6 @@
         rootTask.removeLaunchTickMessages();
     }
 
-    boolean mayFreezeScreenLocked() {
-        return mayFreezeScreenLocked(app);
-    }
-
-    private boolean mayFreezeScreenLocked(WindowProcessController app) {
-        // Only freeze the screen if this activity is currently attached to
-        // an application, and that application is not blocked or unresponding.
-        // In any other case, we can't count on getting the screen unfrozen,
-        // so it is best to leave as-is.
-        return hasProcess() && !app.isCrashing() && !app.isNotResponding();
-    }
-
-    void startFreezingScreenLocked(WindowProcessController app, int configChanges) {
-        if (mayFreezeScreenLocked(app)) {
-            if (getParent() == null) {
-                Slog.w(TAG_WM,
-                        "Attempted to freeze screen with non-existing app token: " + token);
-                return;
-            }
-
-            // Window configuration changes only effect windows, so don't require a screen freeze.
-            int freezableConfigChanges = configChanges & ~(CONFIG_WINDOW_CONFIGURATION);
-            if (freezableConfigChanges == 0 && okToDisplay()) {
-                ProtoLog.v(WM_DEBUG_ORIENTATION, "Skipping set freeze of %s", token);
-                return;
-            }
-
-            startFreezingScreen();
-        }
-    }
-
-    void startFreezingScreen() {
-        startFreezingScreen(ROTATION_UNDEFINED /* overrideOriginalDisplayRotation */);
-    }
-
-    void startFreezingScreen(int overrideOriginalDisplayRotation) {
-        if (mTransitionController.isShellTransitionsEnabled()) {
-            return;
-        }
-        ProtoLog.i(WM_DEBUG_ORIENTATION,
-                "Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s",
-                token, isVisible(), mFreezingScreen, mVisibleRequested,
-                new RuntimeException().fillInStackTrace());
-        if (!mVisibleRequested) {
-            return;
-        }
-
-        // If the override is given, the rotation of display doesn't change but we still want to
-        // cover the activity whose configuration is changing by freezing the display and running
-        // the rotation animation.
-        final boolean forceRotation = overrideOriginalDisplayRotation != ROTATION_UNDEFINED;
-        if (!mFreezingScreen) {
-            mFreezingScreen = true;
-            mWmService.registerAppFreezeListener(this);
-            mWmService.mAppsFreezingScreen++;
-            if (mWmService.mAppsFreezingScreen == 1) {
-                if (forceRotation) {
-                    // Make sure normal rotation animation will be applied.
-                    mDisplayContent.getDisplayRotation().cancelSeamlessRotation();
-                }
-                mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */,
-                        mDisplayContent, overrideOriginalDisplayRotation);
-                mWmService.mH.removeMessages(H.APP_FREEZE_TIMEOUT);
-                mWmService.mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
-            }
-        }
-        if (forceRotation) {
-            // The rotation of the real display won't change, so in order to unfreeze the screen
-            // via {@link #checkAppWindowsReadyToShow}, the windows have to be able to call
-            // {@link WindowState#reportResized} (it is skipped if the window is freezing) to update
-            // the drawn state.
-            return;
-        }
-        final int count = mChildren.size();
-        for (int i = 0; i < count; i++) {
-            final WindowState w = mChildren.get(i);
-            w.onStartFreezingScreen();
-        }
-    }
-
-    boolean isFreezingScreen() {
-        return mFreezingScreen;
-    }
-
-    @Override
-    public void onAppFreezeTimeout() {
-        Slog.w(TAG_WM, "Force clearing freeze: " + this);
-        stopFreezingScreen(true, true);
-    }
-
-    void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
-        if (!mFreezingScreen) {
-            return;
-        }
-        ProtoLog.v(WM_DEBUG_ORIENTATION,
-                "Clear freezing of %s force=%b", this, force);
-        final int count = mChildren.size();
-        boolean unfrozeWindows = false;
-        for (int i = 0; i < count; i++) {
-            final WindowState w = mChildren.get(i);
-            unfrozeWindows |= w.onStopFreezingScreen();
-        }
-        if (force || unfrozeWindows) {
-            ProtoLog.v(WM_DEBUG_ORIENTATION, "No longer freezing: %s", this);
-            mFreezingScreen = false;
-            mWmService.unregisterAppFreezeListener(this);
-            mWmService.mAppsFreezingScreen--;
-            mWmService.mLastFinishedFreezeSource = this;
-        }
-        if (unfreezeSurfaceNow) {
-            if (unfrozeWindows) {
-                mWmService.mWindowPlacerLocked.performSurfacePlacement();
-            }
-            mWmService.stopFreezingDisplayLocked();
-        }
-    }
-
     void onFirstWindowDrawn(WindowState win) {
         firstWindowDrawn = true;
         // stop tracking
@@ -7101,24 +6974,11 @@
             return;
         }
 
-        // The token has now changed state to having all windows shown...  what to do, what to do?
-        if (mFreezingScreen) {
-            showAllWindowsLocked();
-            stopFreezingScreen(false, true);
-            ProtoLog.i(WM_DEBUG_ORIENTATION,
-                    "Setting mOrientationChangeComplete=true because wtoken %s "
-                            + "numInteresting=%d numDrawn=%d",
-                    this, mNumInterestingWindows, mNumDrawnWindows);
-            // This will set mOrientationChangeComplete and cause a pass through layout.
-            setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
-                    "checkAppWindowsReadyToShow: freezingScreen");
-        } else {
-            setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
+        setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
 
-            // We can now show all of the drawn windows!
-            if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
-                showAllWindowsLocked();
-            }
+        // We can now show all of the drawn windows!
+        if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
+            showAllWindowsLocked();
         }
     }
 
@@ -7204,10 +7064,10 @@
 
         if (DEBUG_STARTING_WINDOW_VERBOSE && w == mStartingWindow) {
             Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
-                    + " allDrawn=" + allDrawn + " freezingScreen=" + mFreezingScreen);
+                    + " allDrawn=" + allDrawn);
         }
 
-        if (allDrawn && !mFreezingScreen) {
+        if (allDrawn) {
             return false;
         }
 
@@ -7248,10 +7108,8 @@
                         mNumDrawnWindows++;
 
                         if (DEBUG_VISIBILITY || WM_DEBUG_ORIENTATION.isLogToLogcat()) {
-                            Slog.v(TAG, "tokenMayBeDrawn: "
-                                    + this + " w=" + w + " numInteresting=" + mNumInterestingWindows
-                                    + " freezingScreen=" + mFreezingScreen
-                                    + " mAppFreezing=" + w.mAppFreezing);
+                            Slog.v(TAG, "tokenMayBeDrawn: " + this + " w=" + w
+                                    + " numInteresting=" + mNumInterestingWindows);
                         }
 
                         isInterestingAndDrawn = true;
@@ -8199,8 +8057,6 @@
         }
 
         mDisplayContent.mPinnedTaskController.onCancelFixedRotationTransform();
-        // Perform rotation animation according to the rotation of this activity.
-        startFreezingScreen(originalDisplayRotation);
         // This activity may relaunch or perform configuration change so once it has reported drawn,
         // the screen can be unfrozen.
         ensureActivityConfiguration();
@@ -9384,11 +9240,6 @@
                 mLastReportedConfiguration);
 
         if (shouldRelaunchLocked(changes, mTmpConfig)) {
-            // Aha, the activity isn't handling the change, so DIE DIE DIE.
-            if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
-                    && !mTransitionController.isShellTransitionsEnabled()) {
-                startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
-            }
             final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
                     != getWindowConfiguration().getDisplayRotation()
                     || !mTmpConfig.windowConfiguration.getMaxBounds().equals(
@@ -9396,10 +9247,8 @@
             final boolean isAppResizeOnly = !displayMayChange
                     && (changes & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
                             | CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)) == 0;
-            // Do not preserve window if it is freezing screen because the original window won't be
-            // able to update drawn state that causes freeze timeout.
             // TODO(b/258618073): Always preserve if possible.
-            final boolean preserveWindow = isAppResizeOnly && !mFreezingScreen;
+            final boolean preserveWindow = isAppResizeOnly;
             final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
             if (hasResizeChange) {
                 final boolean isDragResizing = task.isDragResizing();
@@ -9695,7 +9544,6 @@
                 scheduleStopForRestartProcess();
             });
         } else {
-            startFreezingScreen();
             scheduleStopForRestartProcess();
         }
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7321f28..d4f9c090 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -509,7 +509,6 @@
         int changes;
         // If the activity was relaunched to match the new configuration.
         boolean activityRelaunched;
-        boolean mIsUpdating;
     }
 
     /** Current sequencing integer of the configuration, for skipping old configurations. */
@@ -4694,14 +4693,12 @@
             if (values != null) {
                 changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
                 mTmpUpdateConfigurationResult.changes = changes;
-                mTmpUpdateConfigurationResult.mIsUpdating = true;
             }
 
             if (!deferResume) {
                 kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
             }
         } finally {
-            mTmpUpdateConfigurationResult.mIsUpdating = false;
             continueWindowLayout();
         }
         mTmpUpdateConfigurationResult.activityRelaunched = !kept;
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index a972ecb..0a2f685 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1195,27 +1195,12 @@
     private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
             ArrayMap<WindowContainer, Integer> outReasons) {
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                "Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(),
-                mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout());
+                "Checking %d opening apps (timeout=%b)...", apps.size(),
+                mDisplayContent.mAppTransition.isTimeout());
         if (mDisplayContent.mAppTransition.isTimeout()) {
             return true;
         }
-        final ScreenRotationAnimation screenRotationAnimation = mService.mRoot.getDisplayContent(
-                Display.DEFAULT_DISPLAY).getRotationAnimation();
 
-        // Imagine the case where we are changing orientation due to an app transition, but a
-        // previous orientation change is still in progress. We won't process the orientation
-        // change for our transition because we need to wait for the rotation animation to
-        // finish.
-        // If we start the app transition at this point, we will interrupt it halfway with a
-        // new rotation animation after the old one finally finishes. It's better to defer the
-        // app transition.
-        if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()
-                && mDisplayContent.getDisplayRotation().needsUpdate()) {
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                    "Delaying app transition for screen rotation animation to finish");
-            return false;
-        }
         for (int i = 0; i < apps.size(); i++) {
             WindowContainer wc = apps.valueAt(i);
             final ActivityRecord activity = getAppFromContainer(wc);
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 6b6f011..d3fd0e3 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -135,8 +135,7 @@
         // decides not to perform seamless rotation, it only affects whether to use fade animation
         // when the windows are drawn. If the windows are not too slow (after rotation animation is
         // done) to be drawn, the visual result can still look smooth.
-        mHasScreenRotationAnimation =
-                displayContent.getRotationAnimation() != null || mTransitionOp == OP_CHANGE;
+        mHasScreenRotationAnimation = mTransitionOp == OP_CHANGE;
         if (mHasScreenRotationAnimation) {
             // Hide the windows immediately because screen should have been covered by screenshot.
             mHideImmediately = true;
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index b9febb83..119709e 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -1036,8 +1036,10 @@
 
         // Normal apps with visible app window will be allowed to start activity if app switching
         // is allowed, or apps like live wallpaper with non app visible window will be allowed.
+        // The home app can start apps even if app switches are usually disallowed.
         final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
-                || state.mAppSwitchState == APP_SWITCH_FG_ONLY;
+                || state.mAppSwitchState == APP_SWITCH_FG_ONLY
+                || isHomeApp(state.mCallingUid, state.mCallingPackage);
         if (appSwitchAllowedOrFg && state.mCallingUidHasVisibleActivity) {
             return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                     /*background*/ false, "callingUid has visible window");
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5435d8f..60c80a3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -128,7 +128,6 @@
 import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
 import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
 import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
-import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
 import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS;
 import static com.android.server.wm.EventLogTags.IMF_REMOVE_IME_SCREENSHOT;
 import static com.android.server.wm.EventLogTags.IMF_SHOW_IME_SCREENSHOT;
@@ -150,7 +149,6 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.dipToPixel;
 import static com.android.server.wm.WindowState.EXCLUSION_LEFT;
 import static com.android.server.wm.WindowState.EXCLUSION_RIGHT;
@@ -630,8 +628,6 @@
     /** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
     final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
 
-    private ScreenRotationAnimation mScreenRotationAnimation;
-
     /**
      * Sequence number for the current layout pass.
      */
@@ -1594,8 +1590,6 @@
                     mTransitionController.setDisplaySyncMethod(startBounds, endBounds, this);
                     collectDisplayChange(transition);
                 }
-            } else if (mLastHasContent) {
-                mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
             }
             sendNewConfiguration();
         }
@@ -1638,7 +1632,6 @@
         // DisplayContent#updateRotationUnchecked.
         if (mWaitingForConfig) {
             mWaitingForConfig = false;
-            mWmService.mLastFinishedFreezeSource = "config-unchanged";
             setLayoutNeeded();
             mWmService.mWindowPlacerLocked.performSurfacePlacement();
         }
@@ -1647,8 +1640,7 @@
 
     @Override
     boolean onDescendantOrientationChanged(@Nullable WindowContainer requestingContainer) {
-        final Configuration config = updateOrientation(
-                requestingContainer, false /* forceUpdate */);
+        final Configuration config = updateOrientationAndComputeConfig(false /* forceUpdate */);
         // If display rotation class tells us that it doesn't consider app requested orientation,
         // this display won't rotate just because of an app changes its requested orientation. Thus
         // it indicates that this display chooses not to handle this request.
@@ -1696,28 +1688,17 @@
     /**
      * Update orientation of the display, returning a non-null new Configuration if it has
      * changed from the current orientation. If a non-null configuration is returned, someone must
-     * call {@link WindowManagerService#setNewDisplayOverrideConfiguration(Configuration,
-     * DisplayContent)} to tell the window manager it can unfreeze the screen. This will typically
-     * be done by calling {@link #sendNewConfiguration}.
+     * request a change transition or call {@link #sendNewConfiguration}.
      *
-     * @param freezeDisplayWindow Freeze the app window if the orientation is changed.
      * @param forceUpdate See {@link DisplayRotation#updateRotationUnchecked(boolean)}
      */
-    Configuration updateOrientation(WindowContainer<?> freezeDisplayWindow, boolean forceUpdate) {
+    Configuration updateOrientationAndComputeConfig(boolean forceUpdate) {
         if (!mDisplayReady) {
             return null;
         }
 
         Configuration config = null;
         if (updateOrientation(forceUpdate)) {
-            // If we changed the orientation but mOrientationChangeComplete is already true,
-            // we used seamless rotation, and we don't need to freeze the screen.
-            if (freezeDisplayWindow != null && !mWmService.mRoot.mOrientationChangeComplete) {
-                final ActivityRecord activity = freezeDisplayWindow.asActivityRecord();
-                if (activity != null && activity.mayFreezeScreenLocked()) {
-                    activity.startFreezingScreen();
-                }
-            }
             config = new Configuration();
             computeScreenConfiguration(config);
         }
@@ -2254,8 +2235,6 @@
                 mDisplayRotation.isRotatingSeamlessly() && !shellTransitions;
         final Transaction transaction =
                 shellTransitions ? getSyncTransaction() : getPendingTransaction();
-        ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
-                ? null : getRotationAnimation();
         // We need to update our screen size information to match the new rotation. If the rotation
         // has actually changed then this method will return true and, according to the comment at
         // the top of the method, the caller is obligated to call computeNewConfigurationLocked().
@@ -2263,12 +2242,6 @@
         // #computeScreenConfiguration() later.
         updateDisplayAndOrientation(null /* outConfig */);
 
-        // NOTE: We disable the rotation in the emulator because
-        //       it doesn't support hardware OpenGL emulation yet.
-        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
-            screenRotationAnimation.setRotation(transaction, rotation);
-        }
-
         if (!shellTransitions) {
             forAllWindows(w -> {
                 w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
@@ -2932,20 +2905,6 @@
     @ScreenOrientation
     @Override
     int getOrientation() {
-        if (mWmService.mDisplayFrozen) {
-            if (mWmService.mPolicy.isKeyguardLocked()) {
-                // Use the last orientation the while the display is frozen with the keyguard
-                // locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED
-                // window. We don't want to check the show when locked window directly though as
-                // things aren't stable while the display is frozen, for example the window could be
-                // momentarily unavailable due to activity relaunch.
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "Display id=%d is frozen while keyguard locked, return %d",
-                        mDisplayId, getLastOrientation());
-                return getLastOrientation();
-            }
-        }
-
         final int compatOrientation = mAppCompatCameraPolicy.getOrientation();
         if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
             mLastOrientationSource = null;
@@ -3413,12 +3372,10 @@
             mAppTransition.removeAppTransitionTimeoutCallbacks();
             mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
             handleAnimatingStoppedAndTransition();
-            mWmService.stopFreezingDisplayLocked();
             mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer);
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
             mPointerEventDispatcher.dispose();
-            setRotationAnimation(null);
             // Unlink death from remote to clear the reference from binder -> mRemoteInsetsDeath
             // -> this DisplayContent.
             setRemoteInsetsController(null);
@@ -3514,24 +3471,6 @@
         RotationUtils.rotateBounds(inOutBounds, mTmpRect, oldRotation, newRotation);
     }
 
-    public void setRotationAnimation(ScreenRotationAnimation screenRotationAnimation) {
-        final ScreenRotationAnimation prev = mScreenRotationAnimation;
-        mScreenRotationAnimation = screenRotationAnimation;
-        if (prev != null) {
-            prev.kill();
-        }
-
-        // Hide the windows which are not significant in rotation animation. So that the windows
-        // don't need to block the unfreeze time.
-        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
-            startAsyncRotationIfNeeded();
-        }
-    }
-
-    public ScreenRotationAnimation getRotationAnimation() {
-        return mScreenRotationAnimation;
-    }
-
     /**
      * Collects this display into an already-collecting transition.
      */
@@ -3595,12 +3534,6 @@
         }
     }
 
-    /** If the display is in transition, there should be a screenshot covering it. */
-    @Override
-    boolean inTransition() {
-        return mScreenRotationAnimation != null || super.inTransition();
-    }
-
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
             @WindowTracingLogLevel int logLevel) {
@@ -3616,10 +3549,6 @@
         proto.write(DPI, mBaseDisplayDensity);
         mDisplayInfo.dumpDebug(proto, DISPLAY_INFO);
         mDisplayRotation.dumpDebug(proto, DISPLAY_ROTATION);
-        final ScreenRotationAnimation screenRotationAnimation = getRotationAnimation();
-        if (screenRotationAnimation != null) {
-            screenRotationAnimation.dumpDebug(proto, SCREEN_ROTATION_ANIMATION);
-        }
         mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
         proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp);
         if (mTransitionController.isShellTransitionsEnabled()) {
@@ -3766,16 +3695,6 @@
 
         pw.println();
 
-        final ScreenRotationAnimation rotationAnimation = getRotationAnimation();
-        if (rotationAnimation != null) {
-            pw.println("  mScreenRotationAnimation:");
-            rotationAnimation.printTo(subPrefix, pw);
-        } else if (dumpAll) {
-            pw.println("  no ScreenRotationAnimation ");
-        }
-
-        pw.println();
-
         // Dump root task references
         final Task rootHomeTask = getDefaultTaskDisplayArea().getRootHomeTask();
         if (rootHomeTask != null) {
@@ -5068,13 +4987,6 @@
         return win != null;
     }
 
-    void onWindowFreezeTimeout() {
-        Slog.w(TAG_WM, "Window freeze timeout expired.");
-        mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
-
-        mWmService.mWindowPlacerLocked.performSurfacePlacement();
-    }
-
     /**
      * Callbacks when the given type of {@link WindowContainer} animation finished running in the
      * hierarchy.
@@ -5297,8 +5209,7 @@
 
     boolean okToDisplay(boolean ignoreFrozen, boolean ignoreScreenOn) {
         if (mDisplayId == DEFAULT_DISPLAY) {
-            return (!mWmService.mDisplayFrozen || ignoreFrozen)
-                    && mWmService.mDisplayEnabled
+            return mWmService.mDisplayEnabled
                     && (ignoreScreenOn || mWmService.mPolicy.isScreenOn());
         }
         return mDisplayInfo.state == Display.STATE_ON;
@@ -5443,10 +5354,7 @@
             // We skip IME windows so they're processed just above their target.
             // Note that this method check should align with {@link
             // WindowState#applyImeWindowsIfNeeded} in case of any state mismatch.
-            return dc.mImeLayeringTarget != null
-                    // Make sure that the IME window won't be skipped to report that it has
-                    // completed the orientation change.
-                    && !dc.mWmService.mDisplayFrozen;
+            return dc.mImeLayeringTarget != null;
         }
 
         /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */
@@ -6427,14 +6335,12 @@
                     changes = performDisplayOverrideConfigUpdate(values);
                 }
                 mAtmService.mTmpUpdateConfigurationResult.changes = changes;
-                mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = true;
             }
 
             if (!deferResume) {
                 kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
             }
         } finally {
-            mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = false;
             mAtmService.continueWindowLayout();
         }
 
@@ -6490,7 +6396,6 @@
         mCurrentOverrideConfigurationChanges = 0;
         if (mWaitingForConfig) {
             mWaitingForConfig = false;
-            mWmService.mLastFinishedFreezeSource = "new-config";
         }
         mAtmService.addWindowLayoutReasons(
                 ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
@@ -7086,13 +6991,6 @@
         }
 
         @Override
-        public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
-            // It is only needed when freezing display in legacy transition.
-            if (mTransitionController.isShellTransitionsEnabled()) return;
-            continueUpdateOrientationForDiffOrienLaunchingApp();
-        }
-
-        @Override
         public void onAppTransitionTimeoutLocked() {
             continueUpdateOrientationForDiffOrienLaunchingApp();
         }
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index f53bc70..f8c1755 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -22,12 +22,8 @@
 import static android.view.Display.TYPE_EXTERNAL;
 import static android.view.Display.TYPE_OVERLAY;
 import static android.view.Display.TYPE_VIRTUAL;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
 import static com.android.server.wm.DisplayRotationProto.FIXED_TO_USER_ROTATION_MODE;
@@ -41,10 +37,7 @@
 import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_NOSENSOR;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
-import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION;
 
-import android.annotation.AnimRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -104,13 +97,6 @@
 
     private static final int ROTATION_UNDEFINED = -1;
 
-    private static class RotationAnimationPair {
-        @AnimRes
-        int mEnter;
-        @AnimRes
-        int mExit;
-    }
-
     @Nullable
     final FoldController mFoldController;
 
@@ -130,7 +116,6 @@
     private final int mCarDockRotation;
     private final int mDeskDockRotation;
     private final int mUndockedHdmiRotation;
-    private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair();
     private final RotationHistory mRotationHistory = new RotationHistory();
     private final RotationLockHistory mRotationLockHistory = new RotationLockHistory();
 
@@ -540,14 +525,6 @@
                 ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, animation in progress.");
                 return false;
             }
-            if (mService.mDisplayFrozen) {
-                // Even if the screen rotation animation has finished (e.g. isAnimating returns
-                // false), there is still some time where we haven't yet unfrozen the display. We
-                // also need to abort rotation here.
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "Deferring rotation, still finishing previous rotation");
-                return false;
-            }
 
             if (mDisplayContent.mFixedRotationTransitionListener.shouldDeferRotation()) {
                 // Makes sure that after the transition is finished, updateOrientation() can see
@@ -644,17 +621,13 @@
             return true;
         }
 
-        mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
-        mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
-                mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
-
         if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
             // The screen rotation animation uses a screenshot to freeze the screen while windows
             // resize underneath. When we are rotating seamlessly, we allow the elements to
             // transition to their rotated state independently and without a freeze required.
             prepareSeamlessRotation();
         } else {
-            prepareNormalRotationAnimation();
+            cancelSeamlessRotation();
         }
 
         // Give a remote handler (system ui) some time to reposition things.
@@ -695,12 +668,6 @@
         }
     }
 
-    void prepareNormalRotationAnimation() {
-        cancelSeamlessRotation();
-        final RotationAnimationPair anim = selectRotationAnimation();
-        mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
-    }
-
     /**
      * This ensures that normal rotation animation is used. E.g. {@link #mRotatingSeamlessly} was
      * set by previous {@link #updateRotationUnchecked}, but another orientation change happens
@@ -820,79 +787,6 @@
         }
     }
 
-    /**
-     * Returns the animation to run for a rotation transition based on the top fullscreen windows
-     * {@link android.view.WindowManager.LayoutParams#rotationAnimation} and whether it is currently
-     * fullscreen and frontmost.
-     */
-    private RotationAnimationPair selectRotationAnimation() {
-        // If the screen is off or non-interactive, force a jumpcut.
-        final boolean forceJumpcut = !mDisplayPolicy.isScreenOnFully()
-                || !mService.mPolicy.okToAnimate(false /* ignoreScreenOn */);
-        final WindowState topFullscreen = mDisplayPolicy.getTopFullscreenOpaqueWindow();
-        ProtoLog.i(WM_DEBUG_ANIM, "selectRotationAnimation topFullscreen=%s"
-                + " rotationAnimation=%d forceJumpcut=%b",
-                topFullscreen,
-                topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation,
-                forceJumpcut);
-        if (forceJumpcut) {
-            mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
-            mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
-            return mTmpRotationAnim;
-        }
-        if (topFullscreen != null) {
-            int animationHint = topFullscreen.getRotationAnimationHint();
-            if (animationHint < 0 && mDisplayPolicy.isTopLayoutFullscreen()) {
-                animationHint = topFullscreen.getAttrs().rotationAnimation;
-            }
-            switch (animationHint) {
-                case ROTATION_ANIMATION_CROSSFADE:
-                case ROTATION_ANIMATION_SEAMLESS: // Crossfade is fallback for seamless.
-                    mTmpRotationAnim.mExit = R.anim.rotation_animation_xfade_exit;
-                    mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
-                    break;
-                case ROTATION_ANIMATION_JUMPCUT:
-                    mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
-                    mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
-                    break;
-                case ROTATION_ANIMATION_ROTATE:
-                default:
-                    mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
-                    break;
-            }
-        } else {
-            mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
-        }
-        return mTmpRotationAnim;
-    }
-
-    /**
-     * Validate whether the current top fullscreen has specified the same
-     * {@link android.view.WindowManager.LayoutParams#rotationAnimation} value as that being passed
-     * in from the previous top fullscreen window.
-     *
-     * @param exitAnimId exiting resource id from the previous window.
-     * @param enterAnimId entering resource id from the previous window.
-     * @param forceDefault For rotation animations only, if true ignore the animation values and
-     *                     just return false.
-     * @return {@code true} if the previous values are still valid, false if they should be replaced
-     *         with the default.
-     */
-    boolean validateRotationAnimation(int exitAnimId, int enterAnimId, boolean forceDefault) {
-        switch (exitAnimId) {
-            case R.anim.rotation_animation_xfade_exit:
-            case R.anim.rotation_animation_jump_exit:
-                // These are the only cases that matter.
-                if (forceDefault) {
-                    return false;
-                }
-                final RotationAnimationPair anim = selectRotationAnimation();
-                return exitAnimId == anim.mExit && enterAnimId == anim.mEnter;
-            default:
-                return true;
-        }
-    }
-
     void restoreSettings(int userRotationMode, int userRotation, int fixedToUserRotation) {
         mFixedToUserRotation = fixedToUserRotation;
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 7a3eb67..e2b5c83 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -78,11 +78,9 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.WINDOW_FREEZE_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
 import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
 
@@ -200,12 +198,6 @@
     private boolean mSustainedPerformanceModeEnabled = false;
     private boolean mSustainedPerformanceModeCurrent = false;
 
-    // During an orientation change, we track whether all windows have rendered
-    // at the new orientation, and this will be false from changing orientation until that occurs.
-    // For seamless rotation cases this always stays true, as the windows complete their orientation
-    // changes 1 by 1 without disturbing global state.
-    boolean mOrientationChangeComplete = true;
-
     private final Handler mHandler;
 
     private String mCloseSystemDialogsReason;
@@ -841,20 +833,6 @@
             }
         }
 
-        if (mWmService.mDisplayFrozen) {
-            ProtoLog.v(WM_DEBUG_ORIENTATION,
-                    "With display frozen, orientationChangeComplete=%b",
-                    mOrientationChangeComplete);
-        }
-        if (mOrientationChangeComplete) {
-            if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
-                mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
-                mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
-                mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
-            }
-            mWmService.stopFreezingDisplayLocked();
-        }
-
         // Destroy the surface of any windows that are no longer visible.
         i = mWmService.mDestroySurface.size();
         if (i > 0) {
@@ -881,13 +859,11 @@
             }
         }
 
-        if (!mWmService.mDisplayFrozen) {
-            // Post these on a handler such that we don't call into power manager service while
-            // holding the window manager lock to avoid lock contention with power manager lock.
-            mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides)
-                    .sendToTarget();
-            mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
-        }
+        // Post these on a handler such that we don't call into power manager service while
+        // holding the window manager lock to avoid lock contention with power manager lock.
+        mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides)
+                .sendToTarget();
+        mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
 
         if (mSustainedPerformanceModeCurrent != mSustainedPerformanceModeEnabled) {
             mSustainedPerformanceModeEnabled = mSustainedPerformanceModeCurrent;
@@ -902,8 +878,7 @@
         }
 
         if (!mWmService.mWaitingForDrawnCallbacks.isEmpty()
-                || (mOrientationChangeComplete && !isLayoutNeeded()
-                && !mUpdateRotation)) {
+                || (!isLayoutNeeded() && !mUpdateRotation)) {
             mWmService.checkDrawnWindowsLocked();
         }
 
@@ -991,7 +966,7 @@
     private void handleResizingWindows() {
         for (int i = mWmService.mResizingWindows.size() - 1; i >= 0; i--) {
             WindowState win = mWmService.mResizingWindows.get(i);
-            if (win.mAppFreezing || win.getDisplayContent().mWaitingForConfig) {
+            if (win.getDisplayContent().mWaitingForConfig) {
                 // Don't remove this window until rotation has completed and is not waiting for the
                 // complete configuration.
                 continue;
@@ -1092,12 +1067,6 @@
             mUpdateRotation = true;
             doRequest = true;
         }
-        if (mOrientationChangeComplete) {
-            mLastWindowFreezeSource = mWmService.mAnimator.mLastWindowFreezeSource;
-            if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
-                doRequest = true;
-            }
-        }
 
         return doRequest;
     }
@@ -1765,7 +1734,7 @@
         // Force-update the orientation from the WindowManager, since we need the true configuration
         // to send to the client now.
         final Configuration config =
-                displayContent.updateOrientation(starting, true /* forceUpdate */);
+                displayContent.updateOrientationAndComputeConfig(true /* forceUpdate */);
         // Visibilities may change so let the starting activity have a chance to report. Can't do it
         // when visibility is changed in each AppWindowToken because it may trigger wrong
         // configuration push because the visibility of some activities may not be updated yet.
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
deleted file mode 100644
index 4bd294b..0000000
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ /dev/null
@@ -1,825 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.util.RotationUtils.deltaRotation;
-import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.AnimationSpecProto.ROTATE;
-import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA;
-import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA;
-import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING;
-import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.utils.CoordinateTransforms.computeRotationMatrix;
-
-import android.animation.ArgbEvaluator;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.os.Trace;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.DisplayInfo;
-import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
-import android.window.ScreenCapture;
-
-import com.android.internal.R;
-import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.ProtoLog;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-
-import java.io.PrintWriter;
-
-/**
- * This class handles the rotation animation when the device is rotated.
- *
- * <p>
- * The screen rotation animation is composed of 4 different part:
- * <ul>
- * <li> The screenshot: <p>
- *     A screenshot of the whole screen prior the change of orientation is taken to hide the
- *     element resizing below. The screenshot is then animated to rotate and cross-fade to
- *     the new orientation with the content in the new orientation.
- *
- * <li> The windows on the display: <p>y
- *      Once the device is rotated, the screen and its content are in the new orientation. The
- *      animation first rotate the new content into the old orientation to then be able to
- *      animate to the new orientation
- *
- * <li> The Background color frame: <p>
- *      To have the animation seem more seamless, we add a color transitioning background behind the
- *      exiting and entering layouts. We compute the brightness of the start and end
- *      layouts and transition from the two brightness values as grayscale underneath the animation
- *
- * <li> The entering Blackframe: <p>
- *     The enter Blackframe is similar to the exit Blackframe but is only used when a custom
- *     rotation animation is used and matches the new content size instead of the screenshot.
- * </ul>
- *
- * Each part has its own Surface which are then animated by {@link SurfaceAnimator}s.
- */
-class ScreenRotationAnimation {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "ScreenRotationAnimation" : TAG_WM;
-
-    private final Context mContext;
-    private final DisplayContent mDisplayContent;
-    private final float[] mTmpFloats = new float[9];
-    private final Transformation mRotateExitTransformation = new Transformation();
-    private final Transformation mRotateEnterTransformation = new Transformation();
-    // Complete transformations being applied.
-    private final Matrix mSnapshotInitialMatrix = new Matrix();
-    private final WindowManagerService mService;
-    /** Only used for custom animations and not screen rotation. */
-    private SurfaceControl mEnterBlackFrameLayer;
-    /** This layer contains the actual screenshot that is to be faded out. */
-    private SurfaceControl mScreenshotLayer;
-    private SurfaceControl[] mRoundedCornerOverlay;
-    /**
-     * Only used for screen rotation and not custom animations. Layered behind all other layers
-     * to avoid showing any "empty" spots
-     */
-    private SurfaceControl mBackColorSurface;
-    private BlackFrame mEnteringBlackFrame;
-
-    private final int mOriginalRotation;
-    private final int mOriginalWidth;
-    private final int mOriginalHeight;
-    private int mCurRotation;
-
-    // The current active animation to move from the old to the new rotated
-    // state.  Which animation is run here will depend on the old and new
-    // rotations.
-    private Animation mRotateExitAnimation;
-    private Animation mRotateEnterAnimation;
-    private Animation mRotateAlphaAnimation;
-    private boolean mStarted;
-    private boolean mAnimRunning;
-    private boolean mFinishAnimReady;
-    private long mFinishAnimStartTime;
-    private SurfaceRotationAnimationController mSurfaceRotationAnimationController;
-    /** Intensity of light/whiteness of the layout before rotation occurs. */
-    private float mStartLuma;
-    /** Intensity of light/whiteness of the layout after rotation occurs. */
-    private float mEndLuma;
-
-    ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
-        mService = displayContent.mWmService;
-        mContext = mService.mContext;
-        mDisplayContent = displayContent;
-        final Rect currentBounds = displayContent.getBounds();
-        final int width = currentBounds.width();
-        final int height = currentBounds.height();
-
-        // Screenshot does NOT include rotation!
-        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        final int realOriginalRotation = displayInfo.rotation;
-
-        mOriginalRotation = originalRotation;
-        // If the delta is not zero, the rotation of display may not change, but we still want to
-        // apply rotation animation because there should be a top app shown as rotated. So the
-        // specified original rotation customizes the direction of animation to have better look
-        // when restoring the rotated app to the same rotation as current display.
-        final int delta = deltaRotation(originalRotation, realOriginalRotation);
-        final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270;
-        mOriginalWidth = flipped ? height : width;
-        mOriginalHeight = flipped ? width : height;
-        final int logicalWidth = displayInfo.logicalWidth;
-        final int logicalHeight = displayInfo.logicalHeight;
-        final boolean isSizeChanged =
-                logicalWidth > mOriginalWidth == logicalHeight > mOriginalHeight
-                && (logicalWidth != mOriginalWidth || logicalHeight != mOriginalHeight);
-        mSurfaceRotationAnimationController = new SurfaceRotationAnimationController();
-
-        // Check whether the current screen contains any secure content.
-        boolean isSecure = displayContent.hasSecureWindowOnScreen();
-        final int displayId = displayContent.getDisplayId();
-        final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
-
-        try {
-            final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer;
-            if (isSizeChanged) {
-                // Temporarily not skip screenshot for the rounded corner overlays and screenshot
-                // the whole display to include the rounded corner overlays.
-                setSkipScreenshotForRoundedCornerOverlays(false, t);
-                ScreenCapture.LayerCaptureArgs captureArgs =
-                        new ScreenCapture.LayerCaptureArgs.Builder(
-                                displayContent.getSurfaceControl())
-                                .setCaptureSecureLayers(true)
-                                .setAllowProtected(true)
-                                .setSourceCrop(new Rect(0, 0, width, height))
-                                .setHintForSeamlessTransition(true)
-                                .build();
-                screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
-            } else {
-                ScreenCapture.LayerCaptureArgs captureArgs =
-                        new ScreenCapture.LayerCaptureArgs.Builder(
-                                displayContent.getSurfaceControl())
-                                .setCaptureSecureLayers(true)
-                                .setAllowProtected(true)
-                                .setSourceCrop(new Rect(0, 0, width, height))
-                                .setHintForSeamlessTransition(true)
-                                .build();
-                screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
-            }
-
-            if (screenshotBuffer == null) {
-                Slog.w(TAG, "Unable to take screenshot of display " + displayId);
-                return;
-            }
-
-            // If the screenshot contains secure layers, we have to make sure the
-            // screenshot surface we display it in also has FLAG_SECURE so that
-            // the user can not screenshot secure layers via the screenshot surface.
-            if (screenshotBuffer.containsSecureLayers()) {
-                isSecure = true;
-            }
-
-            mBackColorSurface = displayContent.makeChildSurface(null)
-                    .setName("BackColorSurface")
-                    .setColorLayer()
-                    .setCallsite("ScreenRotationAnimation")
-                    .build();
-
-            String name = "RotationLayer";
-            mScreenshotLayer = displayContent.makeOverlay()
-                    .setName(name)
-                    .setOpaque(true)
-                    .setSecure(isSecure)
-                    .setCallsite("ScreenRotationAnimation")
-                    .setBLASTLayer()
-                    .build();
-            // This is the way to tell the input system to exclude this surface from occlusion
-            // detection since we don't have a window for it. We do this because this window is
-            // generated by the system as well as its content.
-            InputMonitor.setTrustedOverlayInputInfo(mScreenshotLayer, t, displayId, name);
-
-            mEnterBlackFrameLayer = displayContent.makeOverlay()
-                    .setName("EnterBlackFrameLayer")
-                    .setContainerLayer()
-                    .setCallsite("ScreenRotationAnimation")
-                    .build();
-
-            HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
-                    "ScreenRotationAnimation#getMedianBorderLuma");
-            mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer,
-                    screenshotBuffer.getColorSpace());
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-
-            t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
-            t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
-            // If hdr layers are on-screen, e.g. picture-in-picture mode, the screenshot of
-            // rotation animation is an sdr image containing tone-mapping hdr content, then
-            // disable dimming effect to get avoid of hdr content being dimmed during animation.
-            t.setDimmingEnabled(mScreenshotLayer, !screenshotBuffer.containsHdrLayers());
-            t.setLayer(mBackColorSurface, -1);
-            t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
-            t.setAlpha(mBackColorSurface, 1);
-            t.setBuffer(mScreenshotLayer, hardwareBuffer);
-            t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
-            t.show(mScreenshotLayer);
-            t.show(mBackColorSurface);
-            hardwareBuffer.close();
-
-            if (mRoundedCornerOverlay != null) {
-                for (SurfaceControl sc : mRoundedCornerOverlay) {
-                    if (sc.isValid()) {
-                        t.hide(sc);
-                    }
-                }
-            }
-
-        } catch (OutOfResourcesException e) {
-            Slog.w(TAG, "Unable to allocate freeze surface", e);
-        }
-
-        // If display size is changed with the same orientation, map the bounds of screenshot to
-        // the new logical display size. Currently pending transaction and RWC#mDisplayTransaction
-        // are merged to global transaction, so it can be synced with display change when calling
-        // DisplayManagerInternal#performTraversal(transaction).
-        if (mScreenshotLayer != null && isSizeChanged) {
-            displayContent.getPendingTransaction().setGeometry(mScreenshotLayer,
-                    new Rect(0, 0, mOriginalWidth, mOriginalHeight),
-                    new Rect(0, 0, logicalWidth, logicalHeight), Surface.ROTATION_0);
-        }
-
-        ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
-                "  FREEZE %s: CREATE", mScreenshotLayer);
-        if (originalRotation == realOriginalRotation) {
-            setRotation(t, realOriginalRotation);
-        } else {
-            // If the given original rotation is different from real original display rotation,
-            // this is playing non-zero degree rotation animation without display rotation change,
-            // so the snapshot doesn't need to be transformed.
-            mCurRotation = realOriginalRotation;
-            mSnapshotInitialMatrix.reset();
-            setRotationTransform(t, mSnapshotInitialMatrix);
-        }
-        t.apply();
-    }
-
-    void setSkipScreenshotForRoundedCornerOverlays(
-            boolean skipScreenshot, SurfaceControl.Transaction t) {
-        mDisplayContent.forAllWindows(w -> {
-            if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) {
-                return;
-            }
-            t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot);
-        }, false);
-        if (!skipScreenshot) {
-            // Use sync apply to apply the change immediately, so that the next
-            // SC.captureDisplay can capture the screen decor layers.
-            t.apply(true /* sync */);
-        }
-    }
-
-    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
-        proto.write(STARTED, mStarted);
-        proto.write(ANIMATION_RUNNING, mAnimRunning);
-        proto.end(token);
-    }
-
-    boolean hasScreenshot() {
-        return mScreenshotLayer != null;
-    }
-
-    private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
-        if (mScreenshotLayer == null) {
-            return;
-        }
-        matrix.getValues(mTmpFloats);
-        float x = mTmpFloats[Matrix.MTRANS_X];
-        float y = mTmpFloats[Matrix.MTRANS_Y];
-        t.setPosition(mScreenshotLayer, x, y);
-        t.setMatrix(mScreenshotLayer,
-                mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
-                mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
-
-        t.setAlpha(mScreenshotLayer, (float) 1.0);
-        t.show(mScreenshotLayer);
-    }
-
-    public void printTo(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer);
-        pw.print(prefix);
-        pw.print("mEnteringBlackFrame=");
-        pw.println(mEnteringBlackFrame);
-        if (mEnteringBlackFrame != null) {
-            mEnteringBlackFrame.printTo(prefix + "  ", pw);
-        }
-        pw.print(prefix); pw.print("mCurRotation="); pw.print(mCurRotation);
-        pw.print(" mOriginalRotation="); pw.println(mOriginalRotation);
-        pw.print(prefix); pw.print("mOriginalWidth="); pw.print(mOriginalWidth);
-        pw.print(" mOriginalHeight="); pw.println(mOriginalHeight);
-        pw.print(prefix); pw.print("mStarted="); pw.print(mStarted);
-        pw.print(" mAnimRunning="); pw.print(mAnimRunning);
-        pw.print(" mFinishAnimReady="); pw.print(mFinishAnimReady);
-        pw.print(" mFinishAnimStartTime="); pw.println(mFinishAnimStartTime);
-        pw.print(prefix); pw.print("mRotateExitAnimation="); pw.print(mRotateExitAnimation);
-        pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println();
-        pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation);
-        pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
-        pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
-        mSnapshotInitialMatrix.dump(pw); pw.println();
-    }
-
-    public void setRotation(SurfaceControl.Transaction t, int rotation) {
-        mCurRotation = rotation;
-
-        // Compute the transformation matrix that must be applied
-        // to the snapshot to make it stay in the same original position
-        // with the current screen rotation.
-        int delta = deltaRotation(rotation, mOriginalRotation);
-        computeRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mSnapshotInitialMatrix);
-        setRotationTransform(t, mSnapshotInitialMatrix);
-    }
-
-    /**
-     * Returns true if animating.
-     */
-    private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration,
-            float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
-        if (mScreenshotLayer == null) {
-            // Can't do animation.
-            return false;
-        }
-        if (mStarted) {
-            return true;
-        }
-
-        mStarted = true;
-
-        // Figure out how the screen has moved from the original rotation.
-        int delta = deltaRotation(mCurRotation, mOriginalRotation);
-
-        final boolean customAnim;
-        if (exitAnim != 0 && enterAnim != 0) {
-            customAnim = true;
-            mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
-            mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
-            mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
-                    R.anim.screen_rotate_alpha);
-        } else {
-            customAnim = false;
-            switch (delta) { /* Counter-Clockwise Rotations */
-                case Surface.ROTATION_0:
-                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_0_exit);
-                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.rotation_animation_enter);
-                    break;
-                case Surface.ROTATION_90:
-                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_plus_90_exit);
-                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_plus_90_enter);
-                    break;
-                case Surface.ROTATION_180:
-                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_180_exit);
-                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_180_enter);
-                    break;
-                case Surface.ROTATION_270:
-                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_minus_90_exit);
-                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_minus_90_enter);
-                    break;
-            }
-        }
-
-        ProtoLog.d(WM_DEBUG_ORIENTATION, "Start rotation animation. customAnim=%s, "
-                        + "mCurRotation=%s, mOriginalRotation=%s",
-                customAnim, Surface.rotationToString(mCurRotation),
-                Surface.rotationToString(mOriginalRotation));
-
-        mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
-        mRotateExitAnimation.restrictDuration(maxAnimationDuration);
-        mRotateExitAnimation.scaleCurrentDuration(animationScale);
-        mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
-        mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
-        mRotateEnterAnimation.scaleCurrentDuration(animationScale);
-
-        mAnimRunning = false;
-        mFinishAnimReady = false;
-        mFinishAnimStartTime = -1;
-
-        if (customAnim) {
-            mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
-            mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
-        }
-
-        if (customAnim && mEnteringBlackFrame == null) {
-            try {
-                Rect outer = new Rect(-finalWidth, -finalHeight,
-                        finalWidth * 2, finalHeight * 2);
-                Rect inner = new Rect(0, 0, finalWidth, finalHeight);
-                mEnteringBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
-                        SCREEN_FREEZE_LAYER_BASE, mDisplayContent, false, mEnterBlackFrameLayer);
-            } catch (OutOfResourcesException e) {
-                Slog.w(TAG, "Unable to allocate black surface", e);
-            }
-        }
-
-        if (customAnim) {
-            mSurfaceRotationAnimationController.startCustomAnimation();
-        } else {
-            mSurfaceRotationAnimationController.startScreenRotationAnimation();
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns true if animating.
-     */
-    public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration,
-            float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
-        if (mScreenshotLayer == null) {
-            // Can't do animation.
-            return false;
-        }
-        if (!mStarted) {
-            mEndLuma = TransitionAnimation.getBorderLuma(mDisplayContent.getWindowingLayer(),
-                    finalWidth, finalHeight);
-            startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
-                    exitAnim, enterAnim);
-        }
-        if (!mStarted) {
-            return false;
-        }
-        mFinishAnimReady = true;
-        return true;
-    }
-
-    public void kill() {
-        if (mSurfaceRotationAnimationController != null) {
-            mSurfaceRotationAnimationController.cancel();
-            mSurfaceRotationAnimationController = null;
-        }
-
-        if (mScreenshotLayer != null) {
-            ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "  FREEZE %s: DESTROY", mScreenshotLayer);
-            SurfaceControl.Transaction t = mService.mTransactionFactory.get();
-            if (mScreenshotLayer.isValid()) {
-                t.remove(mScreenshotLayer);
-            }
-            mScreenshotLayer = null;
-
-            if (mEnterBlackFrameLayer != null) {
-                if (mEnterBlackFrameLayer.isValid()) {
-                    t.remove(mEnterBlackFrameLayer);
-                }
-                mEnterBlackFrameLayer = null;
-            }
-            if (mBackColorSurface != null) {
-                if (mBackColorSurface.isValid()) {
-                    t.remove(mBackColorSurface);
-                }
-                mBackColorSurface = null;
-            }
-
-            if (mRoundedCornerOverlay != null) {
-                if (mDisplayContent.getRotationAnimation() == null
-                        || mDisplayContent.getRotationAnimation() == this) {
-                    setSkipScreenshotForRoundedCornerOverlays(true, t);
-                    for (SurfaceControl sc : mRoundedCornerOverlay) {
-                        if (sc.isValid()) {
-                            t.show(sc);
-                        }
-                    }
-                }
-                mRoundedCornerOverlay = null;
-            }
-            t.apply();
-        }
-
-        if (mEnteringBlackFrame != null) {
-            mEnteringBlackFrame.kill();
-            mEnteringBlackFrame = null;
-        }
-        if (mRotateExitAnimation != null) {
-            mRotateExitAnimation.cancel();
-            mRotateExitAnimation = null;
-        }
-        if (mRotateEnterAnimation != null) {
-            mRotateEnterAnimation.cancel();
-            mRotateEnterAnimation = null;
-        }
-        if (mRotateAlphaAnimation != null) {
-            mRotateAlphaAnimation.cancel();
-            mRotateAlphaAnimation = null;
-        }
-    }
-
-    public boolean isAnimating() {
-        return mSurfaceRotationAnimationController != null
-                && mSurfaceRotationAnimationController.isAnimating();
-    }
-
-    public boolean isRotating() {
-        return mCurRotation != mOriginalRotation;
-    }
-
-    /**
-     * Utility class that runs a {@link ScreenRotationAnimation} on the {@link
-     * SurfaceAnimationRunner}.
-     * <p>
-     * The rotation animation supports both screen rotation and custom animations
-     *
-     * For custom animations:
-     * <ul>
-     *   <li>
-     *     The screenshot layer which has an added animation of it's alpha channel
-     *     ("screen_rotate_alpha") and that will be applied along with the custom animation.
-     *   </li>
-     *   <li> A device layer that is animated with the provided custom animation </li>
-     * </ul>
-     *
-     * For screen rotation:
-     * <ul>
-     *   <li> A rotation layer that is both rotated and faded out during a single animation </li>
-     *   <li> A device layer that is both rotated and faded in during a single animation </li>
-     *   <li> A background color layer that transitions colors behind the first two layers </li>
-     * </ul>
-     *
-     * {@link ScreenRotationAnimation#startAnimation(
-     *     SurfaceControl.Transaction, long, float, int, int, int, int)}.
-     * </ul>
-     *
-     * <p>
-     * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of
-     * this three {@link SurfaceControl}s which then delegates the animation to the
-     * {@link ScreenRotationAnimation}.
-     */
-    class SurfaceRotationAnimationController {
-        private SurfaceAnimator mDisplayAnimator;
-        private SurfaceAnimator mScreenshotRotationAnimator;
-        private SurfaceAnimator mRotateScreenAnimator;
-        private SurfaceAnimator mEnterBlackFrameAnimator;
-
-        void startCustomAnimation() {
-            try {
-                mService.mSurfaceAnimationRunner.deferStartingAnimations();
-                mRotateScreenAnimator = startScreenshotAlphaAnimation();
-                mDisplayAnimator = startDisplayRotation();
-                if (mEnteringBlackFrame != null) {
-                    mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
-                }
-            } finally {
-                mService.mSurfaceAnimationRunner.continueStartingAnimations();
-            }
-        }
-
-        /**
-         * Start the rotation animation of the display and the screenshot on the
-         * {@link SurfaceAnimationRunner}.
-         */
-        void startScreenRotationAnimation() {
-            try {
-                mService.mSurfaceAnimationRunner.deferStartingAnimations();
-                mDisplayAnimator = startDisplayRotation();
-                mScreenshotRotationAnimator = startScreenshotRotationAnimation();
-                startColorAnimation();
-            } finally {
-                mService.mSurfaceAnimationRunner.continueStartingAnimations();
-            }
-        }
-
-        private SimpleSurfaceAnimatable.Builder initializeBuilder() {
-            return new SimpleSurfaceAnimatable.Builder()
-                    .setSyncTransactionSupplier(mDisplayContent::getSyncTransaction)
-                    .setPendingTransactionSupplier(mDisplayContent::getPendingTransaction)
-                    .setCommitTransactionRunnable(mDisplayContent::commitPendingTransaction)
-                    .setAnimationLeashSupplier(mDisplayContent::makeOverlay);
-        }
-
-        private SurfaceAnimator startDisplayRotation() {
-            SurfaceAnimator animator = startAnimation(initializeBuilder()
-                            .setAnimationLeashParent(mDisplayContent.getSurfaceControl())
-                            .setSurfaceControl(mDisplayContent.getWindowingLayer())
-                            .setParentSurfaceControl(mDisplayContent.getSurfaceControl())
-                            .setWidth(mDisplayContent.getSurfaceWidth())
-                            .setHeight(mDisplayContent.getSurfaceHeight())
-                            .build(),
-                    createWindowAnimationSpec(mRotateEnterAnimation),
-                    this::onAnimationEnd);
-
-            // Crop the animation leash to avoid extended wallpaper from showing over
-            // mBackColorSurface
-            Rect displayBounds = mDisplayContent.getBounds();
-            mDisplayContent.getPendingTransaction()
-                    .setWindowCrop(animator.mLeash, displayBounds.width(), displayBounds.height());
-            return animator;
-        }
-
-        private SurfaceAnimator startScreenshotAlphaAnimation() {
-            return startAnimation(initializeBuilder()
-                            .setSurfaceControl(mScreenshotLayer)
-                            .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
-                            .setWidth(mDisplayContent.getSurfaceWidth())
-                            .setHeight(mDisplayContent.getSurfaceHeight())
-                            .build(),
-                    createWindowAnimationSpec(mRotateAlphaAnimation),
-                    this::onAnimationEnd);
-        }
-
-        private SurfaceAnimator startEnterBlackFrameAnimation() {
-            return startAnimation(initializeBuilder()
-                            .setSurfaceControl(mEnterBlackFrameLayer)
-                            .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
-                            .build(),
-                    createWindowAnimationSpec(mRotateEnterAnimation),
-                    this::onAnimationEnd);
-        }
-
-        private SurfaceAnimator startScreenshotRotationAnimation() {
-            return startAnimation(initializeBuilder()
-                            .setSurfaceControl(mScreenshotLayer)
-                            .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
-                            .build(),
-                    createWindowAnimationSpec(mRotateExitAnimation),
-                    this::onAnimationEnd);
-        }
-
-
-        /**
-         * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a
-         * grayscale color
-         */
-        private void startColorAnimation() {
-            int colorTransitionMs = mContext.getResources().getInteger(
-                    R.integer.config_screen_rotation_color_transition);
-            final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner;
-            final float[] rgbTmpFloat = new float[3];
-            final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
-            final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
-            final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale();
-            final ArgbEvaluator va = ArgbEvaluator.getInstance();
-            runner.startAnimation(
-                new LocalAnimationAdapter.AnimationSpec() {
-                    @Override
-                    public long getDuration() {
-                        return duration;
-                    }
-
-                    @Override
-                    public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
-                        long currentPlayTime) {
-                        final float fraction = getFraction(currentPlayTime);
-                        final int color = (Integer) va.evaluate(fraction, startColor, endColor);
-                        Color middleColor = Color.valueOf(color);
-                        rgbTmpFloat[0] = middleColor.red();
-                        rgbTmpFloat[1] = middleColor.green();
-                        rgbTmpFloat[2] = middleColor.blue();
-                        if (leash.isValid()) {
-                            t.setColor(leash, rgbTmpFloat);
-                        }
-                    }
-
-                    @Override
-                    public void dump(PrintWriter pw, String prefix) {
-                        pw.println(prefix + "startLuma=" + mStartLuma
-                                + " endLuma=" + mEndLuma
-                                + " durationMs=" + colorTransitionMs);
-                    }
-
-                    @Override
-                    public void dumpDebugInner(ProtoOutputStream proto) {
-                        final long token = proto.start(ROTATE);
-                        proto.write(START_LUMA, mStartLuma);
-                        proto.write(END_LUMA, mEndLuma);
-                        proto.write(DURATION_MS, colorTransitionMs);
-                        proto.end(token);
-                    }
-                },
-                mBackColorSurface, mDisplayContent.getPendingTransaction(), null);
-        }
-
-        private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) {
-            return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */,
-                    false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */);
-        }
-
-        /**
-         * Start an animation defined by animationSpec on a new {@link SurfaceAnimator}.
-         *
-         * @param animatable The animatable used for the animation.
-         * @param animationSpec The spec of the animation.
-         * @param animationFinishedCallback Callback passed to the {@link SurfaceAnimator}
-         *                                    and called when the animation finishes.
-         * @return The newly created {@link SurfaceAnimator} that as been started.
-         */
-        private SurfaceAnimator startAnimation(
-                SurfaceAnimator.Animatable animatable,
-                LocalAnimationAdapter.AnimationSpec animationSpec,
-                OnAnimationFinishedCallback animationFinishedCallback) {
-            SurfaceAnimator animator = new SurfaceAnimator(
-                    animatable, animationFinishedCallback, mService);
-
-            LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
-                    animationSpec, mService.mSurfaceAnimationRunner);
-            animator.startAnimation(mDisplayContent.getPendingTransaction(),
-                    localAnimationAdapter, false, ANIMATION_TYPE_SCREEN_ROTATION);
-            return animator;
-        }
-
-        private void onAnimationEnd(@AnimationType int type, AnimationAdapter anim) {
-            synchronized (mService.mGlobalLock) {
-                if (isAnimating()) {
-                    ProtoLog.v(WM_DEBUG_ORIENTATION,
-                            "ScreenRotation still animating: type: %d\n"
-                                    + "mDisplayAnimator: %s\n"
-                                    + "mEnterBlackFrameAnimator: %s\n"
-                                    + "mRotateScreenAnimator: %s\n"
-                                    + "mScreenshotRotationAnimator: %s",
-                            type,
-                            mDisplayAnimator != null
-                                    ? mDisplayAnimator.isAnimating() : null,
-                            mEnterBlackFrameAnimator != null
-                                    ? mEnterBlackFrameAnimator.isAnimating() : null,
-                            mRotateScreenAnimator != null
-                                    ? mRotateScreenAnimator.isAnimating() : null,
-                            mScreenshotRotationAnimator != null
-                                    ? mScreenshotRotationAnimator.isAnimating() : null
-                    );
-                    return;
-                }
-                ProtoLog.d(WM_DEBUG_ORIENTATION, "ScreenRotationAnimation onAnimationEnd");
-                mEnterBlackFrameAnimator = null;
-                mScreenshotRotationAnimator = null;
-                mRotateScreenAnimator = null;
-                mService.mAnimator.mBulkUpdateParams |= WindowSurfacePlacer.SET_UPDATE_ROTATION;
-                if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
-                    // It also invokes kill().
-                    mDisplayContent.setRotationAnimation(null);
-                    mDisplayContent.mAppCompatCameraPolicy.onScreenRotationAnimationFinished();
-                } else {
-                    kill();
-                }
-                mService.updateRotation(false, false);
-            }
-        }
-
-        public void cancel() {
-            if (mEnterBlackFrameAnimator != null) {
-                mEnterBlackFrameAnimator.cancelAnimation();
-            }
-            if (mScreenshotRotationAnimator != null) {
-                mScreenshotRotationAnimator.cancelAnimation();
-            }
-
-            if (mRotateScreenAnimator != null) {
-                mRotateScreenAnimator.cancelAnimation();
-            }
-
-            if (mDisplayAnimator != null) {
-                mDisplayAnimator.cancelAnimation();
-            }
-
-            if (mBackColorSurface != null) {
-                mService.mSurfaceAnimationRunner.onAnimationCancelled(mBackColorSurface);
-            }
-        }
-
-        public boolean isAnimating() {
-            return mDisplayAnimator != null && mDisplayAnimator.isAnimating()
-                    || mEnterBlackFrameAnimator != null && mEnterBlackFrameAnimator.isAnimating()
-                    || mRotateScreenAnimator != null && mRotateScreenAnimator.isAnimating()
-                    || mScreenshotRotationAnimator != null
-                    && mScreenshotRotationAnimator.isAnimating();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java b/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
deleted file mode 100644
index 3b3db89..0000000
--- a/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.view.SurfaceControl;
-
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * An implementation of {@link SurfaceAnimator.Animatable} that is instantiated
- * using a builder pattern for more convenience over reimplementing the whole interface.
- * <p>
- * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance of this class.
- *
- * @see com.android.server.wm.SurfaceAnimator.Animatable
- */
-public class SimpleSurfaceAnimatable implements SurfaceAnimator.Animatable {
-    private final int mWidth;
-    private final int mHeight;
-    private final boolean mShouldDeferAnimationFinish;
-    private final SurfaceControl mAnimationLeashParent;
-    private final SurfaceControl mSurfaceControl;
-    private final SurfaceControl mParentSurfaceControl;
-    private final Runnable mCommitTransactionRunnable;
-    private final Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
-    private final Supplier<SurfaceControl.Transaction> mSyncTransaction;
-    private final Supplier<SurfaceControl.Transaction> mPendingTransaction;
-    private final BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated;
-    private final Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost;
-    private final Consumer<Runnable> mOnAnimationFinished;
-
-    /**
-     * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance.
-     */
-    private SimpleSurfaceAnimatable(Builder builder) {
-        mWidth = builder.mWidth;
-        mHeight = builder.mHeight;
-        mShouldDeferAnimationFinish = builder.mShouldDeferAnimationFinish;
-        mAnimationLeashParent = builder.mAnimationLeashParent;
-        mSurfaceControl = builder.mSurfaceControl;
-        mParentSurfaceControl = builder.mParentSurfaceControl;
-        mCommitTransactionRunnable = builder.mCommitTransactionRunnable;
-        mAnimationLeashFactory = builder.mAnimationLeashFactory;
-        mOnAnimationLeashCreated = builder.mOnAnimationLeashCreated;
-        mOnAnimationLeashLost = builder.mOnAnimationLeashLost;
-        mSyncTransaction = builder.mSyncTransactionSupplier;
-        mPendingTransaction = builder.mPendingTransactionSupplier;
-        mOnAnimationFinished = builder.mOnAnimationFinished;
-    }
-
-    @Override
-    public SurfaceControl.Transaction getSyncTransaction() {
-        return mSyncTransaction.get();
-    }
-
-    @NonNull
-    @Override
-    public SurfaceControl.Transaction getPendingTransaction() {
-        return mPendingTransaction.get();
-    }
-
-    @Override
-    public void commitPendingTransaction() {
-        mCommitTransactionRunnable.run();
-    }
-
-    @Override
-    public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
-        if (mOnAnimationLeashCreated != null) {
-            mOnAnimationLeashCreated.accept(t, leash);
-        }
-
-    }
-
-    @Override
-    public void onAnimationLeashLost(SurfaceControl.Transaction t) {
-        if (mOnAnimationLeashLost != null) {
-            mOnAnimationLeashLost.accept(t);
-        }
-    }
-
-    @Override
-    @NonNull
-    public SurfaceControl.Builder makeAnimationLeash() {
-        return mAnimationLeashFactory.get();
-    }
-
-    @Override
-    public SurfaceControl getAnimationLeashParent() {
-        return mAnimationLeashParent;
-    }
-
-    @Override
-    @Nullable
-    public SurfaceControl getSurfaceControl() {
-        return mSurfaceControl;
-    }
-
-    @Override
-    public SurfaceControl getParentSurfaceControl() {
-        return mParentSurfaceControl;
-    }
-
-    @Override
-    public int getSurfaceWidth() {
-        return mWidth;
-    }
-
-    @Override
-    public int getSurfaceHeight() {
-        return mHeight;
-    }
-
-    @Override
-    public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
-        if (mOnAnimationFinished != null) {
-            mOnAnimationFinished.accept(endDeferFinishCallback);
-        }
-        return mShouldDeferAnimationFinish;
-    }
-
-    /**
-     * Builder class to create a {@link SurfaceAnimator.Animatable} without having to
-     * create a new class that implements the interface.
-     */
-    static class Builder {
-        private int mWidth = -1;
-        private int mHeight = -1;
-        private boolean mShouldDeferAnimationFinish = false;
-
-        @Nullable
-        private SurfaceControl mAnimationLeashParent = null;
-
-        @Nullable
-        private SurfaceControl mSurfaceControl = null;
-
-        @Nullable
-        private SurfaceControl mParentSurfaceControl = null;
-        private Runnable mCommitTransactionRunnable;
-
-        @Nullable
-        private BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated =
-                null;
-
-        @Nullable
-        private Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost = null;
-
-        @Nullable
-        private Consumer<Runnable> mOnAnimationFinished = null;
-
-        @NonNull
-        private Supplier<SurfaceControl.Transaction> mSyncTransactionSupplier;
-
-        @NonNull
-        private Supplier<SurfaceControl.Transaction> mPendingTransactionSupplier;
-
-        @NonNull
-        private Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
-
-        /**
-         * Set the runnable to be called when
-         * {@link SurfaceAnimator.Animatable#commitPendingTransaction()}
-         * is called.
-         *
-         * @see SurfaceAnimator.Animatable#commitPendingTransaction()
-         */
-        public SimpleSurfaceAnimatable.Builder setCommitTransactionRunnable(
-                @NonNull Runnable commitTransactionRunnable) {
-            mCommitTransactionRunnable = commitTransactionRunnable;
-            return this;
-        }
-
-        /**
-         * Set the callback called when
-         * {@link SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
-         * SurfaceControl)} is called
-         *
-         * @see SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
-         * SurfaceControl)
-         */
-        public SimpleSurfaceAnimatable.Builder setOnAnimationLeashCreated(
-                @Nullable BiConsumer<SurfaceControl.Transaction, SurfaceControl>
-                        onAnimationLeashCreated) {
-            mOnAnimationLeashCreated = onAnimationLeashCreated;
-            return this;
-        }
-
-        /**
-         * Set the callback called when
-         * {@link SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)}
-         * (SurfaceControl.Transaction, SurfaceControl)} is called
-         *
-         * @see SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)
-         */
-        public SimpleSurfaceAnimatable.Builder setOnAnimationLeashLost(
-                @Nullable Consumer<SurfaceControl.Transaction> onAnimationLeashLost) {
-            mOnAnimationLeashLost = onAnimationLeashLost;
-            return this;
-        }
-
-        /**
-         * @see SurfaceAnimator.Animatable#getSyncTransaction()
-         */
-        public Builder setSyncTransactionSupplier(
-                @NonNull Supplier<SurfaceControl.Transaction> syncTransactionSupplier) {
-            mSyncTransactionSupplier = syncTransactionSupplier;
-            return this;
-        }
-
-        /**
-         * @see SurfaceAnimator.Animatable#getPendingTransaction()
-         */
-        public Builder setPendingTransactionSupplier(
-                @NonNull Supplier<SurfaceControl.Transaction> pendingTransactionSupplier) {
-            mPendingTransactionSupplier = pendingTransactionSupplier;
-            return this;
-        }
-
-        /**
-         * Set the {@link Supplier} responsible for creating a new animation leash.
-         *
-         * @see SurfaceAnimator.Animatable#makeAnimationLeash()
-         */
-        public SimpleSurfaceAnimatable.Builder setAnimationLeashSupplier(
-                @NonNull Supplier<SurfaceControl.Builder> animationLeashFactory) {
-            mAnimationLeashFactory = animationLeashFactory;
-            return this;
-        }
-
-        /**
-         * @see SurfaceAnimator.Animatable#getAnimationLeashParent()
-         */
-        public SimpleSurfaceAnimatable.Builder setAnimationLeashParent(
-                SurfaceControl animationLeashParent) {
-            mAnimationLeashParent = animationLeashParent;
-            return this;
-        }
-
-        /**
-         * @see SurfaceAnimator.Animatable#getSurfaceControl()
-         */
-        public SimpleSurfaceAnimatable.Builder setSurfaceControl(
-                @NonNull SurfaceControl surfaceControl) {
-            mSurfaceControl = surfaceControl;
-            return this;
-        }
-
-        /**
-         * @see SurfaceAnimator.Animatable#getParentSurfaceControl()
-         */
-        public SimpleSurfaceAnimatable.Builder setParentSurfaceControl(
-                SurfaceControl parentSurfaceControl) {
-            mParentSurfaceControl = parentSurfaceControl;
-            return this;
-        }
-
-        /**
-         * Default to -1.
-         *
-         * @see SurfaceAnimator.Animatable#getSurfaceWidth()
-         */
-        public SimpleSurfaceAnimatable.Builder setWidth(int width) {
-            mWidth = width;
-            return this;
-        }
-
-        /**
-         * Default to -1.
-         *
-         * @see SurfaceAnimator.Animatable#getSurfaceHeight()
-         */
-        public SimpleSurfaceAnimatable.Builder setHeight(int height) {
-            mHeight = height;
-            return this;
-        }
-
-        /**
-         * Set the value returned by
-         * {@link SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)}.
-         *
-         * @param onAnimationFinish will be called with the runnable to execute when the animation
-         *                          needs to be finished.
-         * @see SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)
-         */
-        public SimpleSurfaceAnimatable.Builder setShouldDeferAnimationFinish(
-                boolean shouldDeferAnimationFinish,
-                @Nullable Consumer<Runnable> onAnimationFinish) {
-            mShouldDeferAnimationFinish = shouldDeferAnimationFinish;
-            mOnAnimationFinished = onAnimationFinish;
-            return this;
-        }
-
-        public SurfaceAnimator.Animatable build() {
-            if (mSyncTransactionSupplier == null) {
-                throw new IllegalArgumentException("mSyncTransactionSupplier cannot be null");
-            }
-            if (mPendingTransactionSupplier == null) {
-                throw new IllegalArgumentException("mPendingTransactionSupplier cannot be null");
-            }
-            if (mAnimationLeashFactory == null) {
-                throw new IllegalArgumentException("mAnimationLeashFactory cannot be null");
-            }
-            if (mCommitTransactionRunnable == null) {
-                throw new IllegalArgumentException("mCommitTransactionRunnable cannot be null");
-            }
-            if (mSurfaceControl == null) {
-                throw new IllegalArgumentException("mSurfaceControl cannot be null");
-            }
-            return new SimpleSurfaceAnimatable(this);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 1993053..fc7437b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2092,12 +2092,6 @@
                 ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
                 prev = null;
             }
-            // It is possible the activity was freezing the screen before it was paused.
-            // In that case go ahead and remove the freeze this activity has on the screen
-            // since it is no longer visible.
-            if (prev != null) {
-                prev.stopFreezingScreen(true /* unfreezeNow */, true /* force */);
-            }
         }
 
         if (resumeNext) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 790ae1e..80137a2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -146,7 +146,6 @@
         boolean rootAnimating = false;
         mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
         mBulkUpdateParams = 0;
-        root.mOrientationChangeComplete = true;
         if (DEBUG_WINDOW_TRACE) {
             Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
         }
@@ -203,8 +202,7 @@
         }
 
         final boolean hasPendingLayoutChanges = root.hasPendingLayoutChanges(this);
-        final boolean doRequest = (mBulkUpdateParams != 0 || root.mOrientationChangeComplete)
-                && root.copyAnimToLayoutParams();
+        final boolean doRequest = mBulkUpdateParams != 0 && root.copyAnimToLayoutParams();
         if (hasPendingLayoutChanges || doRequest) {
             mService.mWindowPlacerLocked.requestTraversal();
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1754d73..0e9b423 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -31,7 +31,6 @@
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
@@ -116,13 +115,11 @@
 import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
 import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_APPLIED;
-import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.LockGuard.installLock;
 import static com.android.server.policy.PhoneWindowManager.TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerService.DEMOTE_TOP_REASON_EXPANDED_NOTIFICATION_SHADE;
-import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
@@ -151,7 +148,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener;
 import static com.android.server.wm.WindowManagerServiceDumpProto.BACK_NAVIGATION;
-import static com.android.server.wm.WindowManagerServiceDumpProto.DISPLAY_FROZEN;
 import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
 import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_DISPLAY_ID;
 import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_WINDOW;
@@ -251,7 +247,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
-import android.util.TimeUtils;
 import android.util.TypedValue;
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
@@ -403,8 +398,6 @@
 
     static final int LAYOUT_REPEAT_THRESHOLD = 4;
 
-    static final boolean PROFILE_ORIENTATION = false;
-
     /** The maximum length we will accept for a loaded animation duration:
      * this is 10 seconds.
      */
@@ -742,19 +735,8 @@
     @Nullable
     private Runnable mPointerDownOutsideFocusRunnable;
 
-    boolean mDisplayFrozen = false;
-    long mDisplayFreezeTime = 0;
-    int mLastDisplayFreezeDuration = 0;
-    Object mLastFinishedFreezeSource = null;
     boolean mSwitchingUser = false;
 
-    final static int WINDOWS_FREEZING_SCREENS_NONE = 0;
-    final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1;
-    final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2;
-    int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
-
-    int mAppsFreezingScreen = 0;
-
     @VisibleForTesting
     boolean mPerDisplayFocusEnabled;
 
@@ -764,11 +746,6 @@
     // Number of windows whose insets state have been changed.
     int mWindowsInsetsChanged = 0;
 
-    // This is held as long as we have the screen frozen, to give us time to
-    // perform a rotation animation when turning off shows the lock screen which
-    // changes the orientation.
-    private final PowerManager.WakeLock mScreenFrozenLock;
-
     final TaskSnapshotController mTaskSnapshotController;
     final SnapshotController mSnapshotController;
 
@@ -1053,12 +1030,6 @@
 
     final DragDropController mDragDropController;
 
-    /** For frozen screen animations. */
-    private int mExitAnimId, mEnterAnimId;
-
-    /** The display that the rotation animation is applying to. */
-    private int mFrozenDisplayId = INVALID_DISPLAY;
-
     /** Skip repeated ActivityRecords initialization. Note that AppWindowsToken's version of this
      * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
     int mTransactionSequence;
@@ -1161,12 +1132,6 @@
         }
     };
 
-    final ArrayList<AppFreezeListener> mAppFreezeListeners = new ArrayList<>();
-
-    interface AppFreezeListener {
-        void onAppFreezeTimeout();
-    }
-
     private final ScreenRecordingCallbackController mScreenRecordingCallbackController;
 
     private volatile boolean mDisableSecureWindows = false;
@@ -1347,9 +1312,6 @@
             mAnimationsDisabled = mPowerManagerInternal
                     .getLowPowerState(ServiceType.ANIMATION).batterySaverEnabled;
         }
-        mScreenFrozenLock = mPowerManager.newWakeLock(
-                PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
-        mScreenFrozenLock.setReferenceCounted(false);
 
         mRotationWatcherController = new RotationWatcherController(this);
         mDisplayNotificationController = new DisplayWindowListenerController(this);
@@ -5601,11 +5563,8 @@
     // -------------------------------------------------------------
 
     final class H extends android.os.Handler {
-        public static final int WINDOW_FREEZE_TIMEOUT = 11;
-
         public static final int PERSIST_ANIMATION_SCALE = 14;
         public static final int ENABLE_SCREEN = 16;
-        public static final int APP_FREEZE_TIMEOUT = 17;
         public static final int REPORT_WINDOWS_CHANGE = 19;
 
         public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
@@ -5645,14 +5604,6 @@
                 Slog.v(TAG_WM, "handleMessage: entry what=" + msg.what);
             }
             switch (msg.what) {
-                case WINDOW_FREEZE_TIMEOUT: {
-                    final DisplayContent displayContent = (DisplayContent) msg.obj;
-                    synchronized (mGlobalLock) {
-                        displayContent.onWindowFreezeTimeout();
-                    }
-                    break;
-                }
-
                 case PERSIST_ANIMATION_SCALE: {
                     Settings.Global.putFloat(mContext.getContentResolver(),
                             Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
@@ -5691,17 +5642,6 @@
                     break;
                 }
 
-                case APP_FREEZE_TIMEOUT: {
-                    synchronized (mGlobalLock) {
-                        ProtoLog.w(WM_ERROR, "App freeze timeout expired.");
-                        mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
-                        for (int i = mAppFreezeListeners.size() - 1; i >= 0; --i) {
-                            mAppFreezeListeners.get(i).onAppFreezeTimeout();
-                        }
-                    }
-                    break;
-                }
-
                 case REPORT_WINDOWS_CHANGE: {
                     if (mWindowsChanged) {
                         synchronized (mGlobalLock) {
@@ -6310,22 +6250,6 @@
         return win;
     }
 
-    void makeWindowFreezingScreenIfNeededLocked(WindowState w) {
-        // If the screen is currently frozen, then keep it frozen until this window draws at its
-        // new orientation.
-        if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
-                && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
-            ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
-            if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
-                mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
-                // XXX should probably keep timeout from
-                // when we first froze the display.
-                mH.sendNewMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, w.getDisplayContent(),
-                        WINDOW_FREEZE_TIMEOUT_DURATION);
-            }
-        }
-    }
-
     void checkDrawnWindowsLocked() {
         if (mWaitingForDrawnCallbacks.isEmpty()) {
             return;
@@ -6394,188 +6318,6 @@
         return changed;
     }
 
-    void startFreezingDisplay(int exitAnim, int enterAnim) {
-        startFreezingDisplay(exitAnim, enterAnim, getDefaultDisplayContentLocked());
-    }
-
-    void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent) {
-        startFreezingDisplay(exitAnim, enterAnim, displayContent,
-                ROTATION_UNDEFINED /* overrideOriginalRotation */);
-    }
-
-    void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
-            int overrideOriginalRotation) {
-        if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {
-            return;
-        }
-
-        if (!displayContent.isReady() || !displayContent.getDisplayPolicy().isScreenOnFully()
-                || displayContent.getDisplayInfo().state == Display.STATE_OFF
-                || !displayContent.okToAnimate()) {
-            // No need to freeze the screen before the display is ready,  if the screen is off,
-            // or we can't currently animate.
-            return;
-        }
-
-        displayContent.requestDisplayUpdate(() -> {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStartFreezingDisplay");
-            doStartFreezingDisplay(exitAnim, enterAnim, displayContent, overrideOriginalRotation);
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        });
-    }
-
-    private void doStartFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
-            int overrideOriginalRotation) {
-        ProtoLog.d(WM_DEBUG_ORIENTATION,
-                            "startFreezingDisplayLocked: exitAnim=%d enterAnim=%d called by %s",
-                            exitAnim, enterAnim, Debug.getCallers(8));
-        mScreenFrozenLock.acquire();
-        // Apply launch power mode to reduce screen frozen time because orientation change may
-        // relaunch activity and redraw windows. This may also help speed up user switching.
-        mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-
-        mDisplayFrozen = true;
-        mDisplayFreezeTime = SystemClock.elapsedRealtime();
-        mLastFinishedFreezeSource = null;
-
-        // {@link mDisplayFrozen} prevents us from freezing on multiple displays at the same time.
-        // As a result, we only track the display that has initially froze the screen.
-        mFrozenDisplayId = displayContent.getDisplayId();
-
-        mInputManagerCallback.freezeInputDispatchingLw();
-
-        if (displayContent.mAppTransition.isTransitionSet()) {
-            displayContent.mAppTransition.freeze();
-        }
-
-        if (PROFILE_ORIENTATION) {
-            File file = new File("/data/system/frozen");
-            Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
-        }
-
-        mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
-        mExitAnimId = exitAnim;
-        mEnterAnimId = enterAnim;
-
-        final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED
-                ? overrideOriginalRotation
-                : displayContent.getDisplayInfo().rotation;
-        displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent,
-                originalRotation));
-    }
-
-    void stopFreezingDisplayLocked() {
-        if (!mDisplayFrozen) {
-            return;
-        }
-
-        final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId);
-        final int numOpeningApps;
-        final boolean waitingForConfig;
-        final boolean waitingForRemoteDisplayChange;
-        if (displayContent != null) {
-            numOpeningApps = displayContent.mOpeningApps.size();
-            waitingForConfig = displayContent.mWaitingForConfig;
-            waitingForRemoteDisplayChange = displayContent.mRemoteDisplayChangeController
-                    .isWaitingForRemoteDisplayChange();
-        } else {
-            waitingForConfig = waitingForRemoteDisplayChange = false;
-            numOpeningApps = 0;
-        }
-        if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0
-                || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
-                || numOpeningApps > 0) {
-            ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
-                    + "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, "
-                    + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
-                    + "mOpeningApps.size()=%d",
-                    waitingForConfig, waitingForRemoteDisplayChange,
-                    mAppsFreezingScreen, mWindowsFreezingScreen,
-                    numOpeningApps);
-            return;
-        }
-
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStopFreezingDisplayLocked-"
-                + mLastFinishedFreezeSource);
-        doStopFreezingDisplayLocked(displayContent);
-        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-    }
-
-    private void doStopFreezingDisplayLocked(DisplayContent displayContent) {
-        ProtoLog.d(WM_DEBUG_ORIENTATION,
-                    "stopFreezingDisplayLocked: Unfreezing now");
-
-        // We must make a local copy of the displayId as it can be potentially overwritten later on
-        // in this method. For example, {@link startFreezingDisplayLocked} may be called as a result
-        // of update rotation, but we reference the frozen display after that call in this method.
-        mFrozenDisplayId = INVALID_DISPLAY;
-        mDisplayFrozen = false;
-        mInputManagerCallback.thawInputDispatchingLw();
-        mLastDisplayFreezeDuration = (int)(SystemClock.elapsedRealtime() - mDisplayFreezeTime);
-        StringBuilder sb = new StringBuilder(128);
-        sb.append("Screen frozen for ");
-        TimeUtils.formatDuration(mLastDisplayFreezeDuration, sb);
-        if (mLastFinishedFreezeSource != null) {
-            sb.append(" due to ");
-            sb.append(mLastFinishedFreezeSource);
-        }
-        ProtoLog.i(WM_ERROR, "%s", sb.toString());
-        mH.removeMessages(H.APP_FREEZE_TIMEOUT);
-        if (PROFILE_ORIENTATION) {
-            Debug.stopMethodTracing();
-        }
-
-        boolean updateRotation = false;
-
-        ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
-                : displayContent.getRotationAnimation();
-        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
-            ProtoLog.i(WM_DEBUG_ORIENTATION, "**** Dismissing screen rotation animation");
-            DisplayInfo displayInfo = displayContent.getDisplayInfo();
-            // Get rotation animation again, with new top window
-            if (!displayContent.getDisplayRotation().validateRotationAnimation(
-                    mExitAnimId, mEnterAnimId, false /* forceDefault */)) {
-                mExitAnimId = mEnterAnimId = 0;
-            }
-            if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
-                    getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
-                        displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
-                mTransaction.apply();
-            } else {
-                screenRotationAnimation.kill();
-                displayContent.setRotationAnimation(null);
-                updateRotation = true;
-            }
-        } else {
-            if (screenRotationAnimation != null) {
-                screenRotationAnimation.kill();
-                displayContent.setRotationAnimation(null);
-            }
-            updateRotation = true;
-        }
-
-        boolean configChanged;
-
-        // While the display is frozen we don't re-compute the orientation
-        // to avoid inconsistent states.  However, something interesting
-        // could have actually changed during that time so re-evaluate it
-        // now to catch that.
-        configChanged = displayContent != null && displayContent.updateOrientation();
-
-        mScreenFrozenLock.release();
-
-        if (updateRotation && displayContent != null) {
-            ProtoLog.d(WM_DEBUG_ORIENTATION, "Performing post-rotate rotation");
-            configChanged |= displayContent.updateRotationUnchecked();
-        }
-
-        if (configChanged) {
-            displayContent.sendNewConfiguration();
-        }
-        mAtmService.endPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-        mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN);
-    }
-
     static int getPropertyInt(String[] tokens, int index, int defUnits, int defDps,
             DisplayMetrics dm) {
         if (index < tokens.length) {
@@ -6876,7 +6618,6 @@
             if (imeWindow != null) {
                 imeWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
             }
-            proto.write(DISPLAY_FROZEN, mDisplayFrozen);
             proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
             proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
 
@@ -6999,13 +6740,6 @@
             pw.print(' '); pw.print(dc.mMinSizeOfResizeableTaskDp);
         });
         pw.print("  mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
-        pw.print("  mLastDisplayFreezeDuration=");
-                TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
-                if ( mLastFinishedFreezeSource != null) {
-                    pw.print(" due to ");
-                    pw.print(mLastFinishedFreezeSource);
-                }
-                pw.println();
         pw.print("  mDisableSecureWindows="); pw.println(mDisableSecureWindows);
 
         mInputManagerCallback.dump(pw, "  ");
@@ -7025,9 +6759,6 @@
             mRoot.dumpLayoutNeededDisplayIds(pw);
 
             pw.print("  mTransactionSequence="); pw.println(mTransactionSequence);
-            pw.print("  mDisplayFrozen="); pw.print(mDisplayFrozen);
-                    pw.print(" windows="); pw.print(mWindowsFreezingScreen);
-                    pw.print(" apps="); pw.println(mAppsFreezingScreen);
             final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
             pw.print("  mRotation="); pw.println(defaultDisplayContent.getRotation());
             pw.print("  mLastOrientation=");
@@ -8905,16 +8636,6 @@
         }
     }
 
-    void registerAppFreezeListener(AppFreezeListener listener) {
-        if (!mAppFreezeListeners.contains(listener)) {
-            mAppFreezeListeners.add(listener);
-        }
-    }
-
-    void unregisterAppFreezeListener(AppFreezeListener listener) {
-        mAppFreezeListeners.remove(listener);
-    }
-
     /** Called to inform window manager if non-Vr UI shoul be disabled or not. */
     public void disableNonVrUi(boolean disable) {
         synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 26bc09f..30dde54 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -969,14 +969,8 @@
         return canUpdate;
     }
 
+    // TODO(365884835): remove this method with external callers.
     public void stopFreezingActivities() {
-        synchronized (mAtm.mGlobalLock) {
-            int i = mActivities.size();
-            while (i > 0) {
-                i--;
-                mActivities.get(i).stopFreezingScreen(true /* unfreezeNow */, true /* force */);
-            }
-        }
     }
 
     void finishActivities() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index da58470..a8f22ea 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -218,7 +218,6 @@
 import android.util.MergedConfiguration;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -366,7 +365,6 @@
     boolean mPermanentlyHidden; // the window should never be shown again
     // This is a non-system overlay window that is currently force hidden.
     private boolean mForceHideNonSystemOverlayWindow;
-    boolean mAppFreezing;
     boolean mHidden = true;    // Used to determine if to show child windows.
     private boolean mDragResizing;
     private boolean mDragResizingChangeReported = true;
@@ -601,11 +599,6 @@
      */
     int mLastVisibleLayoutRotation = -1;
 
-    /**
-     * How long we last kept the screen frozen.
-     */
-    int mLastFreezeDuration;
-
     /** Is this window now (or just being) removed? */
     boolean mRemoved;
 
@@ -1475,7 +1468,6 @@
 
             consumeInsetsChange();
             onResizeHandled();
-            mWmService.makeWindowFreezingScreenIfNeededLocked(this);
 
             // Reset the drawn state if the window need to redraw for the change, so the transition
             // can wait until it has finished drawing to start.
@@ -1700,7 +1692,7 @@
 
     @Override
     boolean hasContentToDisplay() {
-        if (!mAppFreezing && isDrawn() && (mViewVisibility == View.VISIBLE
+        if (!isDrawn() && (mViewVisibility == View.VISIBLE
                 || (isAnimating(TRANSITION | PARENTS)
                 && !getDisplayContent().mAppTransition.isTransitionSet()))) {
             return true;
@@ -1912,7 +1904,6 @@
      */
     boolean isInteresting() {
         return mActivityRecord != null
-                && (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
                 && mViewVisibility == View.VISIBLE;
     }
 
@@ -2398,12 +2389,12 @@
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                     "Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b "
                             + "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
-                            + "mDisplayFrozen=%b callers=%s",
+                            + "callers=%s",
                     this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit,
                     mHasSurface, mWinAnimator.getShown(),
                     isAnimating(TRANSITION | PARENTS),
                     mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
-                    mWmService.mDisplayFrozen, Debug.getCallers(6));
+                    Debug.getCallers(6));
 
             // First, see if we need to run an animation. If we do, we have to hold off on removing the
             // window until the animation is done. If the display is frozen, just remove immediately,
@@ -3266,32 +3257,6 @@
         }
     }
 
-    void onStartFreezingScreen() {
-        mAppFreezing = true;
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
-            c.onStartFreezingScreen();
-        }
-    }
-
-    boolean onStopFreezingScreen() {
-        boolean unfrozeWindows = false;
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
-            unfrozeWindows |= c.onStopFreezingScreen();
-        }
-
-        if (!mAppFreezing) {
-            return unfrozeWindows;
-        }
-
-        mAppFreezing = false;
-
-        mLastFreezeDuration = 0;
-        setDisplayLayoutNeeded();
-        return true;
-    }
-
     boolean destroySurface(boolean cleanupOnResume, boolean appStopped) {
         boolean destroyedSomething = false;
 
@@ -4192,16 +4157,6 @@
                     + " mDestroying=" + mDestroying
                     + " mRemoved=" + mRemoved);
         }
-        if (mAppFreezing) {
-            pw.println(prefix + " configOrientationChanging="
-                    + (getLastReportedConfiguration().orientation != getConfiguration().orientation)
-                    + " mAppFreezing=" + mAppFreezing);
-        }
-        if (mLastFreezeDuration != 0) {
-            pw.print(prefix + "mLastFreezeDuration=");
-            TimeUtils.formatDuration(mLastFreezeDuration, pw);
-            pw.println();
-        }
         pw.print(prefix + "mForceSeamlesslyRotate=" + mForceSeamlesslyRotate
                 + " seamlesslyRotate: pending=");
         if (mPendingSeamlessRotate != null) {
@@ -4882,7 +4837,7 @@
             c.updateReportedVisibility(results);
         }
 
-        if (mAppFreezing || mViewVisibility != View.VISIBLE
+        if (mViewVisibility != View.VISIBLE
                 || mAttrs.type == TYPE_APPLICATION_STARTING
                 || mDestroying) {
             return;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 298580e..1d8d867 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -303,8 +303,6 @@
 
         resetDrawState();
 
-        mService.makeWindowFreezingScreenIfNeededLocked(w);
-
         int flags = SurfaceControl.HIDDEN;
         final WindowManager.LayoutParams attrs = w.mAttrs;
 
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 911c686..e1f3f0e 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -357,6 +357,7 @@
     void setTouchpadRightClickZoneEnabled(bool enabled);
     void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
     void setTouchpadSystemGesturesEnabled(bool enabled);
+    void setTouchpadAccelerationEnabled(bool enabled);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
     void setShowTouches(bool enabled);
     void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -540,6 +541,10 @@
         // True to enable system gestures (three- and four-finger swipes) on touchpads.
         bool touchpadSystemGesturesEnabled{true};
 
+        // True if the speed of the pointer will increase as the user moves
+        // their finger faster on the touchpad.
+        bool touchpadAccelerationEnabled{true};
+
         // True if a pointer icon should be shown for stylus pointers.
         bool stylusPointerIconEnabled{false};
 
@@ -869,6 +874,7 @@
         outConfig->touchpadThreeFingerTapShortcutEnabled =
                 mLocked.touchpadThreeFingerTapShortcutEnabled;
         outConfig->touchpadSystemGesturesEnabled = mLocked.touchpadSystemGesturesEnabled;
+        outConfig->touchpadAccelerationEnabled = mLocked.touchpadAccelerationEnabled;
 
         outConfig->disabledDevices = mLocked.disabledInputDevices;
 
@@ -1666,6 +1672,21 @@
             InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
 }
 
+void NativeInputManager::setTouchpadAccelerationEnabled(bool enabled) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        if (mLocked.touchpadAccelerationEnabled == enabled) {
+            return;
+        }
+
+        mLocked.touchpadAccelerationEnabled = enabled;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
 void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
     bool refresh = false;
 
@@ -2644,6 +2665,13 @@
     im->setTouchpadSystemGesturesEnabled(enabled);
 }
 
+static void nativeSetTouchpadAccelerationEnabled(JNIEnv* env, jobject nativeImplObj,
+                                                 jboolean enabled) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+    im->setTouchpadAccelerationEnabled(enabled);
+}
+
 static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
@@ -3354,6 +3382,7 @@
         {"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
          (void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
         {"setTouchpadSystemGesturesEnabled", "(Z)V", (void*)nativeSetTouchpadSystemGesturesEnabled},
+        {"setTouchpadAccelerationEnabled", "(Z)V", (void*)nativeSetTouchpadAccelerationEnabled},
         {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
         {"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
         {"reloadCalibration", "()V", (void*)nativeReloadCalibration},
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 4ef37b9..8b9def9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -908,7 +908,7 @@
                 type,
                 false /* resetLockoutRequiresHardwareAuthToken */));
 
-        when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+        when(mSettingObserver.getEnabledForApps(anyInt(), anyInt())).thenReturn(true);
     }
 
     private void setupFace(int id, boolean confirmationAlwaysRequired,
@@ -930,6 +930,6 @@
             }
         });
 
-        when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+        when(mSettingObserver.getEnabledForApps(anyInt(), anyInt())).thenReturn(true);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 88829c1..acca4cc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_ANY_BIOMETRIC;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_CREDENTIAL;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
@@ -588,7 +589,8 @@
         setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
 
         // Disabled in user settings receives onError
-        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+                .thenReturn(false);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                 null /* authenticators */, false /* useDefaultSubtitle */,
                 false /* deviceCredentialAllowed */);
@@ -602,7 +604,8 @@
 
         // Enrolled, not disabled in settings, user requires confirmation in settings
         resetReceivers();
-        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+                .thenReturn(true);
         when(mBiometricService.mSettingObserver.getConfirmationAlwaysRequired(
                 anyInt() /* modality */, anyInt() /* userId */))
                 .thenReturn(true);
@@ -1493,7 +1496,8 @@
     public void testCanAuthenticate_whenBiometricsNotEnabledForApps_returnsHardwareUnavailable()
             throws Exception {
         setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
-        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+                .thenReturn(false);
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                 .thenReturn(true);
 
@@ -1512,7 +1516,8 @@
     @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
     public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
         setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
-        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+                .thenReturn(false);
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                 .thenReturn(true);
 
@@ -1741,7 +1746,8 @@
 
         final int testId = 0;
 
-        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+                .thenReturn(true);
 
         when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
                 .thenReturn(true);
@@ -1947,7 +1953,8 @@
     }
 
     @Test
-    public void testRegisterEnabledOnKeyguardCallback() throws RemoteException {
+    @RequiresFlagsDisabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void testRegisterEnabledOnKeyguardCallback_flagDisabled() throws RemoteException {
         final UserInfo userInfo1 = new UserInfo(0 /* userId */, "user1" /* name */, 0 /* flags */);
         final UserInfo userInfo2 = new UserInfo(10 /* userId */, "user2" /* name */, 0 /* flags */);
         final List<UserInfo> aliveUsers = List.of(userInfo1, userInfo2);
@@ -1957,10 +1964,10 @@
         mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
 
         when(mUserManager.getAliveUsers()).thenReturn(aliveUsers);
-        when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo1.id))
-                .thenReturn(true);
-        when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo2.id))
-                .thenReturn(false);
+        when(mBiometricService.mSettingObserver
+                .getEnabledOnKeyguard(userInfo1.id, TYPE_ANY_BIOMETRIC)).thenReturn(true);
+        when(mBiometricService.mSettingObserver
+                .getEnabledOnKeyguard(userInfo2.id, TYPE_ANY_BIOMETRIC)).thenReturn(false);
         when(callback.asBinder()).thenReturn(mock(IBinder.class));
 
         mBiometricService.mImpl.registerEnabledOnKeyguardCallback(callback);
@@ -1968,8 +1975,42 @@
         waitForIdle();
 
         verify(callback).asBinder();
-        verify(callback).onChanged(true, userInfo1.id);
-        verify(callback).onChanged(false, userInfo2.id);
+        verify(callback).onChanged(true, userInfo1.id, TYPE_ANY_BIOMETRIC);
+        verify(callback).onChanged(false, userInfo2.id, TYPE_ANY_BIOMETRIC);
+        verifyNoMoreInteractions(callback);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void testRegisterEnabledOnKeyguardCallback_flagEnabled() throws RemoteException {
+        final UserInfo userInfo1 = new UserInfo(0 /* userId */, "user1" /* name */, 0 /* flags */);
+        final UserInfo userInfo2 = new UserInfo(10 /* userId */, "user2" /* name */, 0 /* flags */);
+        final List<UserInfo> aliveUsers = List.of(userInfo1, userInfo2);
+        final IBiometricEnabledOnKeyguardCallback callback =
+                mock(IBiometricEnabledOnKeyguardCallback.class);
+
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
+
+        when(mUserManager.getAliveUsers()).thenReturn(aliveUsers);
+        when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo1.id, TYPE_FACE))
+                .thenReturn(true);
+        when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo1.id,
+                TYPE_FINGERPRINT)).thenReturn(true);
+        when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo2.id, TYPE_FACE))
+                .thenReturn(false);
+        when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo2.id,
+                TYPE_FINGERPRINT)).thenReturn(false);
+        when(callback.asBinder()).thenReturn(mock(IBinder.class));
+
+        mBiometricService.mImpl.registerEnabledOnKeyguardCallback(callback);
+
+        waitForIdle();
+
+        verify(callback).asBinder();
+        verify(callback).onChanged(true, userInfo1.id, TYPE_FACE);
+        verify(callback).onChanged(true, userInfo1.id, TYPE_FINGERPRINT);
+        verify(callback).onChanged(false, userInfo2.id, TYPE_FACE);
+        verify(callback).onChanged(false, userInfo2.id, TYPE_FINGERPRINT);
         verifyNoMoreInteractions(callback);
     }
 
@@ -2065,6 +2106,58 @@
                 userId));
     }
 
+    @Test
+    @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void testCanAuthenticate_faceEnabledForApps() throws Exception {
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(0 /* userId */, TYPE_FACE))
+                .thenReturn(true);
+        when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+                .thenReturn(true);
+
+        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+                invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void testCanAuthenticate_faceDisabledForApps() throws Exception {
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(0 /* userId */, TYPE_FACE))
+                .thenReturn(false);
+        when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+                .thenReturn(true);
+
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS,
+                invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void testCanAuthenticate_fingerprintsEnabledForApps() throws Exception {
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(0 /* userId */, TYPE_FINGERPRINT))
+                .thenReturn(true);
+        when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+                .thenReturn(true);
+
+        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+                invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void testCanAuthenticate_fingerprintsDisabledForApps() throws Exception {
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(0 /* userId */, TYPE_FINGERPRINT))
+                .thenReturn(false);
+        when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+                .thenReturn(true);
+
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS,
+                invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+    }
+
     // Helper methods
 
     private int invokeCanAuthenticate(BiometricService service, int authenticators)
@@ -2082,7 +2175,8 @@
         mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
-        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+                .thenReturn(true);
 
         if ((modality & TYPE_FINGERPRINT) != 0) {
             when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
@@ -2115,7 +2209,8 @@
         mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
-        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+                .thenReturn(true);
 
         assertEquals(modalities.length, strengths.length);
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index f6f831f..f8cf21d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -23,6 +23,7 @@
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
 
+import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS;
 import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -96,7 +97,7 @@
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
                 .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
-        when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+        when(mSettingObserver.getEnabledForApps(anyInt(), anyInt())).thenReturn(true);
         when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
                 anyInt())).thenReturn(true);
         when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
@@ -280,7 +281,7 @@
     public void testCalculateByPriority()
             throws Exception {
         when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
-        when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
+        when(mSettingObserver.getEnabledForApps(anyInt(), anyInt())).thenReturn(false);
 
         BiometricSensor faceSensor = getFaceSensor();
         BiometricSensor fingerprintSensor = getFingerprintSensor();
@@ -370,6 +371,58 @@
         assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
     }
 
+    @Test
+    @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void testBiometricsEnabledForApps_fingerprintEnabled_faceDisabled()
+            throws Exception {
+        when(mSettingObserver.getEnabledForApps(USER_ID, TYPE_FINGERPRINT)).thenReturn(true);
+        when(mSettingObserver.getEnabledForApps(USER_ID, TYPE_FACE)).thenReturn(false);
+        when(mTrustManager.isInSignificantPlace()).thenReturn(true);
+
+        final BiometricSensor sensor = getFaceSensor();
+        BiometricSensor fingerprintSensor = getFingerprintSensor();
+        final PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK
+                | BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor, fingerprintSensor), USER_ID, promptInfo,
+                TEST_PACKAGE_NAME, false /* checkDevicePolicyManager */, mContext,
+                mBiometricCameraManager, mUserManager);
+
+        assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+        assertThat(preAuthInfo.eligibleSensors.get(0).modality).isEqualTo(TYPE_FINGERPRINT);
+        assertThat(preAuthInfo.ineligibleSensors).hasSize(1);
+        assertThat(preAuthInfo.ineligibleSensors.get(0).first.modality).isEqualTo(TYPE_FACE);
+        assertThat(preAuthInfo.ineligibleSensors.get(0).second)
+                .isEqualTo(BIOMETRIC_NOT_ENABLED_FOR_APPS);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void testBiometricsEnabledForApps_fingerprintDisabled_faceEnabled()
+            throws Exception {
+        when(mSettingObserver.getEnabledForApps(USER_ID, TYPE_FINGERPRINT)).thenReturn(false);
+        when(mSettingObserver.getEnabledForApps(USER_ID, TYPE_FACE)).thenReturn(true);
+        when(mTrustManager.isInSignificantPlace()).thenReturn(true);
+
+        final BiometricSensor sensor = getFaceSensor();
+        BiometricSensor fingerprintSensor = getFingerprintSensor();
+        final PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK
+                | BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor, fingerprintSensor), USER_ID, promptInfo,
+                TEST_PACKAGE_NAME, false /* checkDevicePolicyManager */, mContext,
+                mBiometricCameraManager, mUserManager);
+
+        assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+        assertThat(preAuthInfo.eligibleSensors.get(0).modality).isEqualTo(TYPE_FACE);
+        assertThat(preAuthInfo.ineligibleSensors).hasSize(1);
+        assertThat(preAuthInfo.ineligibleSensors.get(0).first.modality).isEqualTo(TYPE_FINGERPRINT);
+        assertThat(preAuthInfo.ineligibleSensors.get(0).second)
+                .isEqualTo(BIOMETRIC_NOT_ENABLED_FOR_APPS);
+    }
+
     private BiometricSensor getFingerprintSensor() {
         BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT,
                 TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG,
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 39206dc..3b32701 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -1612,7 +1612,7 @@
                 );
     }
 
-    public void testThrottling() {
+    public void disabled_testThrottling() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
 
         assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1685,7 +1685,7 @@
         assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
     }
 
-    public void testThrottling_rewind() {
+    public void disabled_testThrottling_rewind() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
 
         assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1715,7 +1715,7 @@
         assertEquals(3, mManager.getRemainingCallCount());
     }
 
-    public void testThrottling_perPackage() {
+    public void disabled_testThrottling_perPackage() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
 
         assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1847,7 +1847,7 @@
         });
     }
 
-    public void testThrottling_foreground() throws Exception {
+    public void disabled_testThrottling_foreground() throws Exception {
         prepareCrossProfileDataSet();
 
         dumpsysOnLogcat("Before save & load");
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
index aad06c6..81ebf86 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
@@ -171,11 +171,12 @@
                 .haveRanksInOrder("ms1");
     }
 
-    public void testSetDynamicShortcuts_withManifestShortcuts() {
-        runTestWithManifestShortcuts(() -> testSetDynamicShortcuts_noManifestShortcuts());
+    public void disabled_testSetDynamicShortcuts_withManifestShortcuts() {
+        runTestWithManifestShortcuts(() ->
+                disabled_testAddDynamicShortcuts_noManifestShortcuts());
     }
 
-    public void testAddDynamicShortcuts_noManifestShortcuts() {
+    public void disabled_testAddDynamicShortcuts_noManifestShortcuts() {
         mManager.addDynamicShortcuts(list(
                 shortcut("s1", A1)
         ));
@@ -264,8 +265,8 @@
                 .haveIds("s1", "s2", "s4", "s5", "s10");
     }
 
-    public void testAddDynamicShortcuts_withManifestShortcuts() {
-        runTestWithManifestShortcuts(() -> testAddDynamicShortcuts_noManifestShortcuts());
+    public void disabled_testAddDynamicShortcuts_withManifestShortcuts() {
+        runTestWithManifestShortcuts(() -> disabled_testAddDynamicShortcuts_noManifestShortcuts());
     }
 
     public void testUpdateShortcuts_noManifestShortcuts() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 96fddf1..31b9cf72 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -17,8 +17,10 @@
 package com.android.server.notification;
 
 import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
 import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
 import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
+import static android.app.AutomaticZenRule.TYPE_THEATER;
 import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
 import static android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING;
 import static android.app.Flags.FLAG_MODES_API;
@@ -87,6 +89,7 @@
 import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
 import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
 import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
+import static com.android.server.notification.Flags.FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING;
 import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
 import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
 
@@ -5518,8 +5521,72 @@
                 eq(ORIGIN_INIT_USER));
     }
 
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+    public void testDeviceEffects_allowsGrayscale() {
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        reset(mDeviceEffectsApplier);
+        ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(true)
+                .build();
+        String ruleId = addRuleWithEffects(TYPE_THEATER, grayscale);
+
+        mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+    public void testDeviceEffects_whileDriving_avoidsGrayscale() {
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        reset(mDeviceEffectsApplier);
+        ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(true)
+                .build();
+        String ruleWithGrayscale = addRuleWithEffects(TYPE_THEATER, grayscale);
+        String drivingRule = addRuleWithEffects(TYPE_DRIVING, null);
+
+        mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleWithGrayscale,
+                CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+
+        mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, drivingRule, CONDITION_TRUE,
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), anyInt());
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+    public void testDeviceEffects_whileDrivingWithGrayscale_allowsGrayscale() {
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        reset(mDeviceEffectsApplier);
+        ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(true)
+                .build();
+        String weirdoDrivingWithGrayscale = addRuleWithEffects(TYPE_DRIVING, grayscale);
+
+        mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, weirdoDrivingWithGrayscale,
+                CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+    }
+
     private String addRuleWithEffects(ZenDeviceEffects effects) {
+        return addRuleWithEffects(TYPE_UNKNOWN, effects);
+    }
+
+    private String addRuleWithEffects(int type, @Nullable ZenDeviceEffects effects) {
         AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setPackage(mContext.getPackageName())
+                .setType(type)
                 .setDeviceEffects(effects)
                 .build();
         return mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c88d515..65150e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2006,7 +2006,6 @@
         display.continueUpdateOrientationForDiffOrienLaunchingApp();
         assertTrue(display.isFixedRotationLaunchingApp(activity));
 
-        activity.stopFreezingScreen(true /* unfreezeSurfaceNow */, true /* force */);
         // Simulate the rotation has been updated to previous one, e.g. sensor updates before the
         // remote rotation is completed.
         doReturn(originalRotation).when(displayRotation).rotationForOrientation(
@@ -2015,14 +2014,10 @@
 
         final DisplayInfo rotatedInfo = activity.getFixedRotationTransformDisplayInfo();
         activity.finishFixedRotationTransform();
-        final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
-        assertNotNull(rotationAnim);
 
         // Because the display doesn't rotate, the rotated activity needs to cancel the fixed
         // rotation. There should be a rotation animation to cover the change of activity.
         verify(activity).onCancelFixedRotationTransform(rotatedInfo.rotation);
-        assertTrue(activity.isFreezingScreen());
-        assertFalse(displayRotation.isRotatingSeamlessly());
 
         // Simulate the remote rotation has completed and the configuration doesn't change, then
         // the rotated activity should also be restored by clearing the transform.
@@ -2041,8 +2036,6 @@
         activity.setVisibleRequested(false);
         clearInvocations(activity);
         activity.onCancelFixedRotationTransform(originalRotation);
-        // The implementation of cancellation must be executed.
-        verify(activity).startFreezingScreen(originalRotation);
     }
 
     @Test
@@ -2599,19 +2592,16 @@
         final TestWindowState appWindow = createWindowState(attrs, activity);
         activity.addWindow(appWindow);
         spyOn(appWindow);
-        doNothing().when(appWindow).onStartFreezingScreen();
 
         // Set initial orientation and update.
         activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-        mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */,
-                false /* forceUpdate */);
+        mDisplayContent.updateOrientationAndComputeConfig(false /* forceUpdate */);
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation());
         appWindow.mResizeReported = false;
 
         // Update the orientation to perform 180 degree rotation and check that resize was reported.
         activity.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
-        mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */,
-                false /* forceUpdate */);
+        mDisplayContent.updateOrientationAndComputeConfig(false /* forceUpdate */);
         // In this test, DC will not get config update. Set the waiting flag to false.
         mDisplayContent.mWaitingForConfig = false;
         mWm.mRoot.performSurfacePlacement();
@@ -2635,8 +2625,6 @@
         final TestWindowState appWindow = createWindowState(attrs, activity);
         activity.addWindow(appWindow);
         spyOn(appWindow);
-        doNothing().when(appWindow).onStartFreezingScreen();
-        doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
 
         // Set initial orientation and update.
         performRotation(displayRotation, Surface.ROTATION_90);
@@ -2795,9 +2783,6 @@
         assertEquals(Configuration.ORIENTATION_PORTRAIT, displayConfig.orientation);
         assertEquals(Configuration.ORIENTATION_PORTRAIT, activityConfig.orientation);
 
-        // Unblock the rotation animation, so the further orientation updates won't be ignored.
-        unblockDisplayRotation(activity.mDisplayContent);
-
         final ActivityRecord topActivity = createActivityRecord(activity.getTask());
         topActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d76a907..0964ebe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1714,7 +1714,6 @@
 
     @Test
     public void testFinishFixedRotationNoAppTransitioningTask() {
-        unblockDisplayRotation(mDisplayContent);
         final ActivityRecord app = createActivityRecord(mDisplayContent);
         final Task task = app.getTask();
         final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
@@ -1742,7 +1741,6 @@
     public void testFixedRotationWithPip() {
         final DisplayContent displayContent = mDefaultDisplay;
         displayContent.setIgnoreOrientationRequest(false);
-        unblockDisplayRotation(displayContent);
         // Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded.
         doNothing().when(displayContent).prepareAppTransition(anyInt());
         // Make resume-top really update the activity state.
@@ -1762,7 +1760,6 @@
 
         assertTrue(displayContent.hasTopFixedRotationLaunchingApp());
         assertTrue(displayContent.mPinnedTaskController.shouldDeferOrientationChange());
-        verify(mWm, never()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
         clearInvocations(pinnedTask);
 
         // Assume that the PiP enter animation is done then the new bounds are set. Expect the
@@ -1799,7 +1796,6 @@
 
     @Test
     public void testNoFixedRotationOnResumedScheduledApp() {
-        unblockDisplayRotation(mDisplayContent);
         final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
         app.setVisible(false);
         app.setState(ActivityRecord.State.RESUMED, "test");
@@ -1811,7 +1807,7 @@
         // The condition should reject using fixed rotation because the resumed client in real case
         // might get display info immediately. And the fixed rotation adjustments haven't arrived
         // client side so the info may be inconsistent with the requested orientation.
-        verify(mDisplayContent).updateOrientation(eq(app), anyBoolean());
+        verify(mDisplayContent).updateOrientationAndComputeConfig(anyBoolean());
         assertFalse(app.isFixedRotationTransforming());
         assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
     }
@@ -1863,9 +1859,6 @@
 
     @Test
     public void testSecondaryInternalDisplayRotationFollowsDefaultDisplay() {
-        // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
-        doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
         final DisplayRotationCoordinator coordinator =
                 mRootWindowContainer.getDisplayRotationCoordinator();
         final DisplayContent defaultDisplayContent = mDisplayContent;
@@ -1921,9 +1914,6 @@
 
     @Test
     public void testSecondaryNonInternalDisplayDoesNotFollowDefaultDisplay() {
-        // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
-        doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
         final DisplayRotationCoordinator coordinator =
                 mRootWindowContainer.getDisplayRotationCoordinator();
 
@@ -1994,20 +1984,11 @@
                     }
                 };
 
-        // kill any existing rotation animation (vestigial from test setup).
-        dc.setRotationAnimation(null);
-
         mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */);
-        // If remote rotation is not finished, the display should not be able to unfreeze.
-        mWm.stopFreezingDisplayLocked();
-        assertTrue(mWm.mDisplayFrozen);
 
         assertTrue(called[0]);
         waitUntilHandlersIdle();
         assertTrue(continued[0]);
-
-        mWm.stopFreezingDisplayLocked();
-        assertFalse(mWm.mDisplayFrozen);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index f6f473b..cff172f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -855,12 +855,6 @@
         startingApp.updateResizingWindowIfNeeded();
         assertTrue(mWm.mResizingWindows.contains(startingApp));
         assertTrue(startingApp.isDrawn());
-
-        // Even if the display is frozen, invisible requested window should not be affected.
-        mWm.startFreezingDisplay(0, 0, mDisplayContent);
-        startingApp.getWindowFrames().setInsetsChanged(true);
-        startingApp.updateResizingWindowIfNeeded();
-        assertTrue(startingApp.isDrawn());
     }
 
     @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 37d2a75..2c390c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1010,20 +1010,6 @@
         waitUntilWindowAnimatorIdle();
     }
 
-    /**
-     * Avoids rotating screen disturbed by some conditions. It is usually used for the default
-     * display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
-     *
-     * @see DisplayRotation#updateRotationUnchecked
-     */
-    void unblockDisplayRotation(DisplayContent dc) {
-        dc.mOpeningApps.clear();
-        mWm.mAppsFreezingScreen = 0;
-        mWm.stopFreezingDisplayLocked();
-        // The rotation animation won't actually play, it needs to be cleared manually.
-        dc.setRotationAnimation(null);
-    }
-
     static void resizeDisplay(DisplayContent displayContent, int width, int height) {
         displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity,
                 displayContent.mBaseDisplayPhysicalXDpi, displayContent.mBaseDisplayPhysicalYDpi);