Merge "Don't include all frames in the face help message deferral logic" into udc-qpr-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 0a7bffc..4b4e512 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -1118,6 +1118,7 @@
             }
             boolean needFileMigration = false;
             long nowElapsed = sElapsedRealtimeClock.millis();
+            int numDuplicates = 0;
             synchronized (mLock) {
                 for (File file : files) {
                     final AtomicFile aFile = createJobFile(file);
@@ -1126,6 +1127,16 @@
                         if (jobs != null) {
                             for (int i = 0; i < jobs.size(); i++) {
                                 JobStatus js = jobs.get(i);
+                                final JobStatus existingJob = this.jobSet.get(
+                                        js.getUid(), js.getNamespace(), js.getJobId());
+                                if (existingJob != null) {
+                                    numDuplicates++;
+                                    // Jobs are meant to have unique uid-namespace-jobId
+                                    // combinations, but we've somehow read multiple jobs with the
+                                    // combination. Drop the latter one since keeping both will
+                                    // result in other issues.
+                                    continue;
+                                }
                                 js.prepareLocked();
                                 js.enqueueTime = nowElapsed;
                                 this.jobSet.add(js);
@@ -1174,6 +1185,10 @@
                 migrateJobFilesAsync();
             }
 
+            if (numDuplicates > 0) {
+                Slog.wtf(TAG, "Encountered " + numDuplicates + " duplicate persisted jobs");
+            }
+
             // Log the count immediately after loading from boot.
             mCurrentJobSetSize = numJobs;
             mScheduledJob30MinHighWaterMark = mCurrentJobSetSize;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 827600c..85f5395 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -247,6 +247,7 @@
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true");
         DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
+        DEFAULT_FLAGS.put("settings_press_hold_nav_handle_to_search", "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 363e554..cba95a7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9236,8 +9236,8 @@
             }
 
             while (parentGroup != null && !parentGroup.isImportantForAutofill()) {
-                ignoredParentLeft += parentGroup.mLeft;
-                ignoredParentTop += parentGroup.mTop;
+                ignoredParentLeft += parentGroup.mLeft - parentGroup.mScrollX;
+                ignoredParentTop += parentGroup.mTop - parentGroup.mScrollY;
 
                 viewParent = parentGroup.getParent();
                 if (viewParent instanceof View) {
diff --git a/core/res/res/anim-ldrtl/activity_close_enter.xml b/core/res/res/anim-ldrtl/activity_close_enter.xml
index 6a699e7..0b48646 100644
--- a/core/res/res/anim-ldrtl/activity_close_enter.xml
+++ b/core/res/res/anim-ldrtl/activity_close_enter.xml
@@ -31,7 +31,7 @@
         android:duration="450" />
 
     <translate
-        android:fromXDelta="10%"
+        android:fromXDelta="96dp"
         android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
@@ -41,11 +41,11 @@
         android:duration="450" />
 
     <extend
-        android:fromExtendLeft="10%"
+        android:fromExtendLeft="96dp"
         android:fromExtendTop="0"
         android:fromExtendRight="0"
         android:fromExtendBottom="0"
-        android:toExtendLeft="10%"
+        android:toExtendLeft="96dp"
         android:toExtendTop="0"
         android:toExtendRight="0"
         android:toExtendBottom="0"
diff --git a/core/res/res/anim-ldrtl/activity_close_exit.xml b/core/res/res/anim-ldrtl/activity_close_exit.xml
index 06a0d69..5277b9f 100644
--- a/core/res/res/anim-ldrtl/activity_close_exit.xml
+++ b/core/res/res/anim-ldrtl/activity_close_exit.xml
@@ -32,7 +32,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="-10%"
+        android:toXDelta="-96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
@@ -43,11 +43,11 @@
     <extend
         android:fromExtendLeft="0"
         android:fromExtendTop="0"
-        android:fromExtendRight="10%"
+        android:fromExtendRight="96dp"
         android:fromExtendBottom="0"
         android:toExtendLeft="0"
         android:toExtendTop="0"
-        android:toExtendRight="10%"
+        android:toExtendRight="96dp"
         android:toExtendBottom="0"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
         android:startOffset="0"
diff --git a/core/res/res/anim-ldrtl/activity_open_enter.xml b/core/res/res/anim-ldrtl/activity_open_enter.xml
index 7b18294..97d2cf9 100644
--- a/core/res/res/anim-ldrtl/activity_open_enter.xml
+++ b/core/res/res/anim-ldrtl/activity_open_enter.xml
@@ -30,7 +30,7 @@
         android:duration="83" />
 
     <translate
-        android:fromXDelta="-10%"
+        android:fromXDelta="-96dp"
         android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
@@ -41,11 +41,11 @@
     <extend
         android:fromExtendLeft="0"
         android:fromExtendTop="0"
-        android:fromExtendRight="10%"
+        android:fromExtendRight="96dp"
         android:fromExtendBottom="0"
         android:toExtendLeft="0"
         android:toExtendTop="0"
-        android:toExtendRight="10%"
+        android:toExtendRight="96dp"
         android:toExtendBottom="0"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
         android:startOffset="0"
diff --git a/core/res/res/anim-ldrtl/activity_open_exit.xml b/core/res/res/anim-ldrtl/activity_open_exit.xml
index c29509e..2159029 100644
--- a/core/res/res/anim-ldrtl/activity_open_exit.xml
+++ b/core/res/res/anim-ldrtl/activity_open_exit.xml
@@ -31,7 +31,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="10%"
+        android:toXDelta="96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
@@ -40,11 +40,11 @@
         android:duration="450" />
 
     <extend
-        android:fromExtendLeft="10%"
+        android:fromExtendLeft="9dp"
         android:fromExtendTop="0"
         android:fromExtendRight="0"
         android:fromExtendBottom="0"
-        android:toExtendLeft="10%"
+        android:toExtendLeft="96dp"
         android:toExtendTop="0"
         android:toExtendRight="0"
         android:toExtendBottom="0"
diff --git a/core/res/res/anim-ldrtl/task_fragment_close_exit.xml b/core/res/res/anim-ldrtl/task_fragment_close_exit.xml
index c5a3654..58fcb1f 100644
--- a/core/res/res/anim-ldrtl/task_fragment_close_exit.xml
+++ b/core/res/res/anim-ldrtl/task_fragment_close_exit.xml
@@ -30,7 +30,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="-10%"
+        android:toXDelta="-96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
diff --git a/core/res/res/anim/activity_close_enter.xml b/core/res/res/anim/activity_close_enter.xml
index 0fefb51..22a1dd6 100644
--- a/core/res/res/anim/activity_close_enter.xml
+++ b/core/res/res/anim/activity_close_enter.xml
@@ -31,7 +31,7 @@
         android:duration="450" />
 
     <translate
-        android:fromXDelta="-10%"
+        android:fromXDelta="-96dp"
         android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
@@ -43,11 +43,11 @@
     <extend
         android:fromExtendLeft="0"
         android:fromExtendTop="0"
-        android:fromExtendRight="10%"
+        android:fromExtendRight="96dp"
         android:fromExtendBottom="0"
         android:toExtendLeft="0"
         android:toExtendTop="0"
-        android:toExtendRight="10%"
+        android:toExtendRight="96dp"
         android:toExtendBottom="0"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
         android:startOffset="0"
diff --git a/core/res/res/anim/activity_close_exit.xml b/core/res/res/anim/activity_close_exit.xml
index f807c26..a671049 100644
--- a/core/res/res/anim/activity_close_exit.xml
+++ b/core/res/res/anim/activity_close_exit.xml
@@ -32,7 +32,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="10%"
+        android:toXDelta="96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
@@ -41,11 +41,11 @@
         android:duration="450" />
 
     <extend
-        android:fromExtendLeft="10%"
+        android:fromExtendLeft="96dp"
         android:fromExtendTop="0"
         android:fromExtendRight="0"
         android:fromExtendBottom="0"
-        android:toExtendLeft="10%"
+        android:toExtendLeft="96dp"
         android:toExtendTop="0"
         android:toExtendRight="0"
         android:toExtendBottom="0"
diff --git a/core/res/res/anim/activity_open_enter.xml b/core/res/res/anim/activity_open_enter.xml
index 1674dab..f3172e4 100644
--- a/core/res/res/anim/activity_open_enter.xml
+++ b/core/res/res/anim/activity_open_enter.xml
@@ -30,7 +30,7 @@
         android:duration="83" />
 
     <translate
-        android:fromXDelta="10%"
+        android:fromXDelta="96dp"
         android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
@@ -39,11 +39,11 @@
         android:duration="450" />
 
     <extend
-        android:fromExtendLeft="10%"
+        android:fromExtendLeft="96dp"
         android:fromExtendTop="0"
         android:fromExtendRight="0"
         android:fromExtendBottom="0"
-        android:toExtendLeft="10%"
+        android:toExtendLeft="96dp"
         android:toExtendTop="0"
         android:toExtendRight="0"
         android:toExtendBottom="0"
diff --git a/core/res/res/anim/activity_open_exit.xml b/core/res/res/anim/activity_open_exit.xml
index 372f2c8..d84827b 100644
--- a/core/res/res/anim/activity_open_exit.xml
+++ b/core/res/res/anim/activity_open_exit.xml
@@ -31,7 +31,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="-10%"
+        android:toXDelta="-96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
@@ -42,11 +42,11 @@
     <extend
         android:fromExtendLeft="0"
         android:fromExtendTop="0"
-        android:fromExtendRight="10%"
+        android:fromExtendRight="96dp"
         android:fromExtendBottom="0"
         android:toExtendLeft="0"
         android:toExtendTop="0"
-        android:toExtendRight="10%"
+        android:toExtendRight="96dp"
         android:toExtendBottom="0"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
         android:startOffset="0"
diff --git a/core/res/res/anim/task_fragment_close_exit.xml b/core/res/res/anim/task_fragment_close_exit.xml
index 84d8b7e..6454085 100644
--- a/core/res/res/anim/task_fragment_close_exit.xml
+++ b/core/res/res/anim/task_fragment_close_exit.xml
@@ -30,7 +30,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="10%"
+        android:toXDelta="96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
diff --git a/core/res/res/anim/task_fragment_open_enter.xml b/core/res/res/anim/task_fragment_open_enter.xml
index 87ee179..5f57ed5 100644
--- a/core/res/res/anim/task_fragment_open_enter.xml
+++ b/core/res/res/anim/task_fragment_open_enter.xml
@@ -27,7 +27,7 @@
         android:startOffset="50"
         android:duration="83" />
     <translate
-        android:fromXDelta="10%"
+        android:fromXDelta="96dp"
         android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index e27cd97..31092536 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -439,8 +439,10 @@
 key usage 0x0c0173 MEDIA_AUDIO_TRACK
 key usage 0x0c019C PROFILE_SWITCH
 key usage 0x0c01A2 ALL_APPS
-key usage 0x0d0044 STYLUS_BUTTON_PRIMARY
-key usage 0x0d005a STYLUS_BUTTON_SECONDARY
+# TODO(b/297094448): Add stylus button mappings as a fallback when we have a way to determine
+#   if a device can actually report it.
+# key usage 0x0d0044 STYLUS_BUTTON_PRIMARY
+# key usage 0x0d005a STYLUS_BUTTON_SECONDARY
 
 # Joystick and game controller axes.
 # Axes that are not mapped will be assigned generic axis numbers by the input subsystem.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 4640106..9b80063 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -245,8 +245,8 @@
 
     private boolean shouldShowBackdrop(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change) {
-        final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE,
-                mTransitionAnimation, false);
+        final Animation a = loadAttributeAnimation(info.getType(), info, change,
+                WALLPAPER_TRANSITION_NONE, mTransitionAnimation, false);
         return a != null && a.getShowBackdrop();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 7bf0893..c111ce6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -34,6 +34,7 @@
 import android.view.Display;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -59,6 +60,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /**
@@ -80,6 +82,9 @@
 
     private static final String TAG = "CompatUIController";
 
+    // The time to wait before education and button hiding
+    private static final int DISAPPEAR_DELAY_MS = 5000;
+
     /** Whether the IME is shown on display id. */
     private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1);
 
@@ -158,6 +163,9 @@
     @NonNull
     private final CompatUIShellCommandHandler mCompatUIShellCommandHandler;
 
+    @NonNull
+    private final Function<Integer, Integer> mDisappearTimeSupplier;
+
     @Nullable
     private CompatUICallback mCompatUICallback;
 
@@ -176,7 +184,8 @@
             @NonNull Lazy<Transitions> transitionsLazy,
             @NonNull DockStateReader dockStateReader,
             @NonNull CompatUIConfiguration compatUIConfiguration,
-            @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler) {
+            @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
+            @NonNull AccessibilityManager accessibilityManager) {
         mContext = context;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -189,6 +198,8 @@
         mDockStateReader = dockStateReader;
         mCompatUIConfiguration = compatUIConfiguration;
         mCompatUIShellCommandHandler = compatUIShellCommandHandler;
+        mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
+                DISAPPEAR_DELAY_MS, flags);
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -510,7 +521,8 @@
             ShellTaskOrganizer.TaskListener taskListener) {
         return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue,
                 taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
-                mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed);
+                mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed,
+                mDisappearTimeSupplier);
     }
 
     private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo,
@@ -556,7 +568,8 @@
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         return new UserAspectRatioSettingsWindowManager(context, taskInfo, mSyncQueue,
                 taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
-                mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor);
+                mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor,
+                mDisappearTimeSupplier);
     }
 
     private void launchUserAspectRatioSettings(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index 9de3f9d..5612bc8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -28,6 +28,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
@@ -37,15 +38,13 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 
 import java.util.function.BiConsumer;
+import java.util.function.Function;
 
 /**
  * Window manager for the reachability education
  */
 class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
 
-    // The time to wait before hiding the education
-    private static final long DISAPPEAR_DELAY_MS = 4000L;
-
     private static final int REACHABILITY_LEFT_OR_UP_POSITION = 0;
     private static final int REACHABILITY_RIGHT_OR_BOTTOM_POSITION = 2;
 
@@ -77,6 +76,8 @@
 
     private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback;
 
+    private final Function<Integer, Integer> mDisappearTimeSupplier;
+
     @Nullable
     @VisibleForTesting
     ReachabilityEduLayout mLayout;
@@ -85,7 +86,8 @@
             SyncTransactionQueue syncQueue,
             ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
             CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor,
-            BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback) {
+            BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback,
+            Function<Integer, Integer> disappearTimeSupplier) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
         mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
@@ -95,6 +97,7 @@
         mCompatUIConfiguration = compatUIConfiguration;
         mMainExecutor = mainExecutor;
         mOnDismissCallback = onDismissCallback;
+        mDisappearTimeSupplier = disappearTimeSupplier;
     }
 
     @Override
@@ -215,7 +218,12 @@
     }
 
     void updateHideTime() {
-        mNextHideTime = SystemClock.uptimeMillis() + DISAPPEAR_DELAY_MS;
+        mNextHideTime = SystemClock.uptimeMillis() + getDisappearTimeMs();
+    }
+
+    private long getDisappearTimeMs() {
+        return mDisappearTimeSupplier.apply(
+                AccessibilityManager.FLAG_CONTENT_ICONS | AccessibilityManager.FLAG_CONTENT_TEXT);
     }
 
     private void updateVisibilityOfViews() {
@@ -248,14 +256,15 @@
                     availableHeight, mCompatUIConfiguration, lastTaskInfo);
             if (!mHasLetterboxSizeChanged) {
                 updateHideTime();
-                mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS);
+                final long disappearTimeMs = getDisappearTimeMs();
+                mMainExecutor.executeDelayed(this::hideReachability, disappearTimeMs);
                 // If reachability education has been seen for the first time, trigger callback to
                 // display aspect ratio settings button once reachability education disappears
                 if (hasShownHorizontalReachabilityEduFirstTime(hasSeenHorizontalReachabilityEdu)
                         || hasShownVerticalReachabilityEduFirstTime(
                         hasSeenVerticalReachabilityEdu)) {
                     mMainExecutor.executeDelayed(this::triggerOnDismissCallback,
-                            DISAPPEAR_DELAY_MS);
+                            disappearTimeMs);
                 }
             }
             mHasUserDoubleTapped = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index bd53dc7..cbff464 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -26,6 +26,7 @@
 import android.os.SystemClock;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
@@ -36,6 +37,7 @@
 import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
 
 import java.util.function.BiConsumer;
+import java.util.function.Function;
 
 /**
  * Window manager for the user aspect ratio settings button which allows users to go to
@@ -45,12 +47,12 @@
 
     private static final long SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 500L;
 
-    private static final long HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 4000L;
-
     private long mNextButtonHideTimeMs = -1L;
 
     private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnButtonClicked;
 
+    private final Function<Integer, Integer> mDisappearTimeSupplier;
+
     private final ShellExecutor mShellExecutor;
 
     @VisibleForTesting
@@ -69,12 +71,14 @@
             @Nullable ShellTaskOrganizer.TaskListener taskListener,
             @NonNull DisplayLayout displayLayout, @NonNull CompatUIHintsState compatUIHintsState,
             @NonNull BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onButtonClicked,
-            @NonNull ShellExecutor shellExecutor) {
+            @NonNull ShellExecutor shellExecutor,
+            @NonNull Function<Integer, Integer> disappearTimeSupplier) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mShellExecutor = shellExecutor;
         mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
         mCompatUIHintsState = compatUIHintsState;
         mOnButtonClicked = onButtonClicked;
+        mDisappearTimeSupplier = disappearTimeSupplier;
     }
 
     @Override
@@ -140,9 +144,9 @@
             return;
         }
         mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
-        mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
-        mShellExecutor.executeDelayed(this::hideUserAspectRatioButton,
-                HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+        final long disappearTimeMs = getDisappearTimeMs();
+        mNextButtonHideTimeMs = updateHideTime(disappearTimeMs);
+        mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs);
     }
 
     @Override
@@ -167,9 +171,9 @@
         if (mHasUserAspectRatioSettingsButton) {
             mShellExecutor.executeDelayed(this::showUserAspectRatioButton,
                     SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
-            mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
-            mShellExecutor.executeDelayed(this::hideUserAspectRatioButton,
-                    HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+            final long disappearTimeMs = getDisappearTimeMs();
+            mNextButtonHideTimeMs = updateHideTime(disappearTimeMs);
+            mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs);
         } else {
             mShellExecutor.removeCallbacks(this::showUserAspectRatioButton);
             mShellExecutor.execute(this::hideUserAspectRatioButton);
@@ -208,4 +212,8 @@
                 && (taskInfo.topActivityBoundsLetterboxed
                     || taskInfo.isUserFullscreenOverrideEnabled);
     }
+
+    private long getDisappearTimeMs() {
+        return mDisappearTimeSupplier.apply(AccessibilityManager.FLAG_CONTENT_CONTROLS);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index aafd9fd..b454807 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.view.IWindowManager;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.launcher3.icons.IconProvider;
@@ -232,10 +233,12 @@
             DisplayImeController imeController, SyncTransactionQueue syncQueue,
             @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy,
             DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
-            CompatUIShellCommandHandler compatUIShellCommandHandler) {
+            CompatUIShellCommandHandler compatUIShellCommandHandler,
+            AccessibilityManager accessibilityManager) {
         return new CompatUIController(context, shellInit, shellController, displayController,
                 displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
-                dockStateReader, compatUIConfiguration, compatUIShellCommandHandler);
+                dockStateReader, compatUIConfiguration, compatUIShellCommandHandler,
+                accessibilityManager);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 7df658e..d310ae3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -37,8 +37,12 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
@@ -334,6 +338,10 @@
         boolean isDisplayRotationAnimationStarted = false;
         final boolean isDreamTransition = isDreamTransition(info);
         final boolean isOnlyTranslucent = isOnlyTranslucent(info);
+        final boolean isActivityReplace = checkActivityReplacement(info, startTransaction);
+        // Some patterns (eg. activity "replacement") require us to re-interpret the type
+        @WindowManager.TransitionType final int transitType =
+                isActivityReplace ? TRANSIT_OPEN : info.getType();
 
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
@@ -430,7 +438,8 @@
             // Don't animate anything that isn't independent.
             if (!TransitionInfo.isIndependent(change, info)) continue;
 
-            Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition);
+            Animation a = loadAnimation(transitType, info, change, wallpaperTransit,
+                    isDreamTransition);
             if (a != null) {
                 if (isTask) {
                     final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
@@ -604,6 +613,53 @@
         return (translucentOpen + translucentClose) > 0;
     }
 
+    /**
+     * Checks for an edge-case where an activity calls finish() followed immediately by
+     * startActivity() to "replace" itself. If in this case, it will swap the layer of the
+     * close/open activities and return `true`. This way, we pretend like we are just "opening"
+     * the new activity.
+     */
+    private static boolean checkActivityReplacement(@NonNull TransitionInfo info,
+            SurfaceControl.Transaction t) {
+        if (info.getType() != TRANSIT_CLOSE) {
+            return false;
+        }
+        int closing = -1;
+        int opening = -1;
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if ((change.getTaskInfo() != null || change.hasFlags(FLAG_IS_DISPLAY))
+                    && !TransitionUtil.isOrderOnly(change)) {
+                // This isn't an activity-level transition.
+                return false;
+            }
+            if (change.getTaskInfo() != null
+                    && change.hasFlags(FLAG_IS_DISPLAY | FLAGS_IS_NON_APP_WINDOW)) {
+                // Ignore non-activity containers.
+                continue;
+            }
+            if (TransitionUtil.isClosingType(change.getMode())) {
+                closing = i;
+            } else if (change.getMode() == TRANSIT_OPEN) {
+                // OPEN implies that it is a new launch. If going "back" the opening app will be
+                // TO_FRONT
+                opening = i;
+            } else if (change.getMode() == TRANSIT_TO_FRONT) {
+                // Normal "going back", so not a replacement.
+                return false;
+            }
+        }
+        if (closing < 0 || opening < 0) {
+            return false;
+        }
+        // Swap the opening and closing z-orders since we're swapping the transit type.
+        final int numChanges = info.getChanges().size();
+        final int zSplitLine = numChanges + 1;
+        t.setLayer(info.getChanges().get(opening).getLeash(), zSplitLine + numChanges - opening);
+        t.setLayer(info.getChanges().get(closing).getLeash(), zSplitLine - closing);
+        return true;
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -656,12 +712,11 @@
     }
 
     @Nullable
-    private Animation loadAnimation(@NonNull TransitionInfo info,
+    private Animation loadAnimation(int type, @NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, int wallpaperTransit,
             boolean isDreamTransition) {
         Animation a;
 
-        final int type = info.getType();
         final int flags = info.getFlags();
         final int changeMode = change.getMode();
         final int changeFlags = change.getFlags();
@@ -716,8 +771,8 @@
             // If there's a scene-transition, then jump-cut.
             return null;
         } else {
-            a = loadAttributeAnimation(
-                    info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition);
+            a = loadAttributeAnimation(type, info, change, wallpaperTransit, mTransitionAnimation,
+                    isDreamTransition);
         }
 
         if (a != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index d978eaf..7b1ce2f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -45,6 +45,7 @@
 import android.graphics.Shader;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
 import android.view.animation.Animation;
 import android.view.animation.Transformation;
 import android.window.ScreenCapture;
@@ -61,10 +62,10 @@
 
     /** Loads the animation that is defined through attribute id for the given transition. */
     @Nullable
-    public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
-            @NonNull TransitionInfo.Change change, int wallpaperTransit,
-            @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) {
-        final int type = info.getType();
+    public static Animation loadAttributeAnimation(@WindowManager.TransitionType int type,
+            @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
+            int wallpaperTransit, @NonNull TransitionAnimation transitionAnimation,
+            boolean isDreamTransition) {
         final int changeMode = change.getMode();
         final int changeFlags = change.getFlags();
         final boolean enter = TransitionUtil.isOpeningType(changeMode);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
index 367676f..f7a060f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
@@ -46,5 +46,7 @@
         default void onStateChangeProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
 
         default void onStateChangeFinished() {}
+
+        default void onFoldStateChanged(boolean isFolded) {}
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 2eb6e71..68b5a81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -63,6 +63,7 @@
     @Nullable
     private IBinder mTransition;
 
+    private boolean mAnimationFinished = false;
     private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>();
 
     public UnfoldTransitionHandler(ShellInit shellInit,
@@ -132,6 +133,13 @@
 
         startTransaction.apply();
         mFinishCallback = finishCallback;
+
+        // Shell transition started when unfold animation has already finished,
+        // finish shell transition immediately
+        if (mAnimationFinished) {
+            finishTransitionIfNeeded();
+        }
+
         return true;
     }
 
@@ -161,17 +169,8 @@
 
     @Override
     public void onStateChangeFinished() {
-        if (mFinishCallback == null) return;
-
-        for (int i = 0; i < mAnimators.size(); i++) {
-            final UnfoldTaskAnimator animator = mAnimators.get(i);
-            animator.clearTasks();
-            animator.stop();
-        }
-
-        mFinishCallback.onTransitionFinished(null);
-        mFinishCallback = null;
-        mTransition = null;
+        mAnimationFinished = true;
+        finishTransitionIfNeeded();
     }
 
     @Override
@@ -218,4 +217,25 @@
     public boolean willHandleTransition() {
         return mTransition != null;
     }
+
+    @Override
+    public void onFoldStateChanged(boolean isFolded) {
+        if (isFolded) {
+            mAnimationFinished = false;
+        }
+    }
+
+    private void finishTransitionIfNeeded() {
+        if (mFinishCallback == null) return;
+
+        for (int i = 0; i < mAnimators.size(); i++) {
+            final UnfoldTaskAnimator animator = mAnimators.get(i);
+            animator.clearTasks();
+            animator.stop();
+        }
+
+        mFinishCallback.onTransitionFinished(null);
+        mFinishCallback = null;
+        mTransition = null;
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index efc69ebd..9b9600e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -41,6 +41,7 @@
 import android.testing.AndroidTestingRunner;
 import android.view.InsetsSource;
 import android.view.InsetsState;
+import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -113,6 +114,9 @@
     @Mock
     private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
 
+    @Mock
+    private AccessibilityManager mAccessibilityManager;
+
     @Captor
     ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
 
@@ -139,7 +143,7 @@
         mController = new CompatUIController(mContext, mShellInit, mMockShellController,
                 mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
                 mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
-                mCompatUIConfiguration, mCompatUIShellCommandHandler) {
+                mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager) {
             @Override
             CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
                     ShellTaskOrganizer.TaskListener taskListener) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index a802f15a..5867a85 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -109,6 +109,6 @@
     private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) {
         return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue,
                 mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor,
-                mOnDismissCallback);
+                mOnDismissCallback, flags -> 0);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
index 1fee153..ce1290b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -93,7 +93,7 @@
         mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
                 mSyncTransactionQueue, mTaskListener, new DisplayLayout(),
                 new CompatUIController.CompatUIHintsState(),
-                mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor());
+                mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor(), flags -> 0);
 
         mLayout = (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate(
                 R.layout.user_aspect_ratio_settings_layout, null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index b48538c..08cc2f7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -106,7 +106,7 @@
                 false, /* topActivityBoundsLetterboxed */ true);
         mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
                 mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
-                mOnUserAspectRatioSettingsButtonClicked, mExecutor);
+                mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0);
         spyOn(mWindowManager);
         doReturn(mLayout).when(mWindowManager).inflateLayout();
         doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
new file mode 100644
index 0000000..63a685e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.unfold;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
+import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
+import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class UnfoldTransitionHandlerTest {
+
+    private UnfoldTransitionHandler mUnfoldTransitionHandler;
+
+    private final TestShellUnfoldProgressProvider mShellUnfoldProgressProvider =
+            new TestShellUnfoldProgressProvider();
+    private final TestTransactionPool mTransactionPool = new TestTransactionPool();
+
+    private FullscreenUnfoldTaskAnimator mFullscreenUnfoldTaskAnimator;
+    private SplitTaskUnfoldAnimator mSplitTaskUnfoldAnimator;
+    private Transitions mTransitions;
+
+    private final IBinder mTransition = new Binder();
+
+    @Before
+    public void before() {
+        final ShellExecutor executor = new TestSyncExecutor();
+        final ShellInit shellInit = new ShellInit(executor);
+
+        mFullscreenUnfoldTaskAnimator = mock(FullscreenUnfoldTaskAnimator.class);
+        mSplitTaskUnfoldAnimator = mock(SplitTaskUnfoldAnimator.class);
+        mTransitions = mock(Transitions.class);
+
+        mUnfoldTransitionHandler = new UnfoldTransitionHandler(
+                shellInit,
+                mShellUnfoldProgressProvider,
+                mFullscreenUnfoldTaskAnimator,
+                mSplitTaskUnfoldAnimator,
+                mTransactionPool,
+                executor,
+                mTransitions
+        );
+
+        shellInit.init();
+    }
+
+    @Test
+    public void handleRequest_physicalDisplayChange_handlesTransition() {
+        ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+        TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+                Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+        TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
+                triggerTaskInfo, /* remoteTransition= */ null, displayChange);
+
+        WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition,
+                requestInfo);
+
+        assertThat(result).isNotNull();
+    }
+
+    @Test
+    public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() {
+        ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+        TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+                Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(false);
+        TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
+                triggerTaskInfo, /* remoteTransition= */ null, displayChange);
+
+        WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition,
+                requestInfo);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void startAnimation_animationHasNotFinishedYet_doesNotFinishTheTransition() {
+        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+
+        verify(finishCallback, never()).onTransitionFinished(any());
+    }
+
+    @Test
+    public void startAnimation_animationFinishes_finishesTheTransition() {
+        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+        mShellUnfoldProgressProvider.onStateChangeStarted();
+        mShellUnfoldProgressProvider.onStateChangeFinished();
+
+        verify(finishCallback).onTransitionFinished(any());
+    }
+
+    @Test
+    public void startAnimation_animationIsAlreadyFinished_finishesTheTransition() {
+        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+        mShellUnfoldProgressProvider.onStateChangeStarted();
+        mShellUnfoldProgressProvider.onStateChangeFinished();
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+
+        verify(finishCallback).onTransitionFinished(any());
+    }
+
+    @Test
+    public void startAnimationSecondTimeAfterFold_animationAlreadyFinished_finishesTransition() {
+        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+        // First unfold
+        mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false);
+        mShellUnfoldProgressProvider.onStateChangeStarted();
+        mShellUnfoldProgressProvider.onStateChangeFinished();
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+        clearInvocations(finishCallback);
+
+        // Fold
+        mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ true);
+
+        // Second unfold
+        mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false);
+        mShellUnfoldProgressProvider.onStateChangeStarted();
+        mShellUnfoldProgressProvider.onStateChangeFinished();
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+
+        verify(finishCallback).onTransitionFinished(any());
+    }
+
+    private TransitionRequestInfo createUnfoldTransitionRequestInfo() {
+        ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+        TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+                Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+        return new TransitionRequestInfo(TRANSIT_CHANGE,
+                triggerTaskInfo, /* remoteTransition= */ null, displayChange);
+    }
+
+    private static class TestShellUnfoldProgressProvider implements ShellUnfoldProgressProvider,
+            ShellUnfoldProgressProvider.UnfoldListener {
+
+        private final List<UnfoldListener> mListeners = new ArrayList<>();
+
+        @Override
+        public void addListener(Executor executor, UnfoldListener listener) {
+            mListeners.add(listener);
+        }
+
+        @Override
+        public void onFoldStateChanged(boolean isFolded) {
+            mListeners.forEach(unfoldListener -> unfoldListener.onFoldStateChanged(isFolded));
+        }
+
+        @Override
+        public void onStateChangeFinished() {
+            mListeners.forEach(UnfoldListener::onStateChangeFinished);
+        }
+
+        @Override
+        public void onStateChangeProgress(float progress) {
+            mListeners.forEach(unfoldListener -> unfoldListener.onStateChangeProgress(progress));
+        }
+
+        @Override
+        public void onStateChangeStarted() {
+            mListeners.forEach(UnfoldListener::onStateChangeStarted);
+        }
+    }
+
+    private static class TestTransactionPool extends TransactionPool {
+        @Override
+        public SurfaceControl.Transaction acquire() {
+            return mock(SurfaceControl.Transaction.class);
+        }
+
+        @Override
+        public void release(SurfaceControl.Transaction t) {
+        }
+    }
+
+    private static class TestSyncExecutor implements ShellExecutor {
+        @Override
+        public void execute(Runnable runnable) {
+            runnable.run();
+        }
+
+        @Override
+        public void executeDelayed(Runnable runnable, long delayMillis) {
+            runnable.run();
+        }
+
+        @Override
+        public void removeCallbacks(Runnable runnable) {
+        }
+
+        @Override
+        public boolean hasCallback(Runnable runnable) {
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index cda919f..4a79a98 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -267,17 +267,22 @@
     }
 
     /**
-     * Set a pending intent for your media button receiver to allow restarting
-     * playback after the session has been stopped. If your app is started in
-     * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
-     * the pending intent.
-     * <p>
-     * The pending intent is recommended to be explicit to follow the security recommendation of
-     * {@link PendingIntent#getActivity}.
+     * Set a pending intent for your media button receiver to allow restarting playback after the
+     * session has been stopped.
+     *
+     * <p>If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be
+     * sent via the pending intent.
+     *
+     * <p>The provided {@link PendingIntent} must not target an activity. Passing an activity
+     * pending intent will cause the call to be ignored. Refer to this <a
+     * href="https://developer.android.com/guide/components/activities/background-starts">guide</a>
+     * for more information.
+     *
+     * <p>The pending intent is recommended to be explicit to follow the security recommendation of
+     * {@link PendingIntent#getService}.
      *
      * @param mbr The {@link PendingIntent} to send the media button event to.
      * @see PendingIntent#getActivity
-     *
      * @deprecated Use {@link #setMediaButtonBroadcastReceiver(ComponentName)} instead.
      */
     @Deprecated
@@ -285,7 +290,7 @@
         try {
             mBinder.setMediaButtonReceiver(mbr);
         } catch (RemoteException e) {
-            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+            e.rethrowFromSystemServer();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index e3de8c7..a9928d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -131,7 +131,7 @@
             mLiveData.observeForever(mObserver);
         }
         mConfigurationController.addCallback(mConfigurationListener);
-        mDumpManager.registerDumpable(
+        mDumpManager.registerNormalDumpable(
                 TAG + "@" + Integer.toHexString(
                         KeyguardSliceViewController.this.hashCode()),
                 KeyguardSliceViewController.this);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6d68eef..8b23b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -285,11 +285,7 @@
     /** Enables preview loading animation in the wallpaper picker. */
     // TODO(b/274443705): Tracking Bug
     @JvmField
-    val WALLPAPER_PICKER_PREVIEW_ANIMATION =
-            unreleasedFlag(
-                    "wallpaper_picker_preview_animation",
-                teamfood = true
-            )
+    val WALLPAPER_PICKER_PREVIEW_ANIMATION = releasedFlag("wallpaper_picker_preview_animation")
 
     /** Stop running face auth when the display state changes to OFF. */
     // TODO(b/294221702): Tracking bug.
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 6c8190a..d0d37c4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -25,6 +25,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
@@ -52,6 +53,7 @@
     private BrightnessMirrorController mMirrorController;
     private boolean mTracking;
     private final FalsingManager mFalsingManager;
+    private final UiEventLogger mUiEventLogger;
 
     private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
         @Override
@@ -72,9 +74,11 @@
 
     BrightnessSliderController(
             BrightnessSliderView brightnessSliderView,
-            FalsingManager falsingManager) {
+            FalsingManager falsingManager,
+            UiEventLogger uiEventLogger) {
         super(brightnessSliderView);
         mFalsingManager = falsingManager;
+        mUiEventLogger = uiEventLogger;
     }
 
     /**
@@ -206,7 +210,7 @@
         @Override
         public void onStartTrackingTouch(SeekBar seekBar) {
             mTracking = true;
-
+            mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH);
             if (mListener != null) {
                 mListener.onChanged(mTracking, getValue(), false);
             }
@@ -220,7 +224,7 @@
         @Override
         public void onStopTrackingTouch(SeekBar seekBar) {
             mTracking = false;
-
+            mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH);
             if (mListener != null) {
                 mListener.onChanged(mTracking, getValue(), true);
             }
@@ -237,10 +241,12 @@
     public static class Factory {
 
         private final FalsingManager mFalsingManager;
+        private final UiEventLogger mUiEventLogger;
 
         @Inject
-        public Factory(FalsingManager falsingManager) {
+        public Factory(FalsingManager falsingManager, UiEventLogger uiEventLogger) {
             mFalsingManager = falsingManager;
+            mUiEventLogger = uiEventLogger;
         }
 
         /**
@@ -250,11 +256,13 @@
          * @param viewRoot the {@link ViewGroup} that will contain the hierarchy. The inflated
          *                 hierarchy will not be attached
          */
-        public BrightnessSliderController create(Context context, @Nullable ViewGroup viewRoot) {
+        public BrightnessSliderController create(
+                Context context,
+                @Nullable ViewGroup viewRoot) {
             int layout = getLayout();
             BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
                     .inflate(layout, viewRoot, false);
-            return new BrightnessSliderController(root, mFalsingManager);
+            return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger);
         }
 
         /** Get the layout to inflate based on what slider to use */
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
new file mode 100644
index 0000000..3a30880
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+public enum BrightnessSliderEvent implements UiEventLogger.UiEventEnum {
+
+    @UiEvent(doc = "slider started to track touch")
+    SLIDER_STARTED_TRACKING_TOUCH(1472),
+    @UiEvent(doc = "slider stopped tracking touch")
+    SLIDER_STOPPED_TRACKING_TOUCH(1473);
+
+    private final int mId;
+
+    BrightnessSliderEvent(int id) {
+        mId = id;
+    }
+
+    @Override
+    public int getId() {
+        return mId;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
index f040d0a..369f9ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
@@ -31,6 +31,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dependency
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.time.SystemClock
 import java.text.FieldPosition
@@ -80,6 +81,7 @@
 class VariableDateViewController(
     private val systemClock: SystemClock,
     private val broadcastDispatcher: BroadcastDispatcher,
+    private val shadeLogger: ShadeLogger,
     private val timeTickHandler: Handler,
     view: VariableDateView
 ) : ViewController<VariableDateView>(view) {
@@ -107,24 +109,29 @@
 
     private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
+            val action = intent.action
+            if (
+                    Intent.ACTION_LOCALE_CHANGED == action ||
+                    Intent.ACTION_TIMEZONE_CHANGED == action
+            ) {
+                // need to get a fresh date format
+                dateFormat = null
+                shadeLogger.d("VariableDateViewController received intent to refresh date format")
+            }
+
+            val handler = mView.handler
+
             // If the handler is null, it means we received a broadcast while the view has not
             // finished being attached or in the process of being detached.
             // In that case, do not post anything.
-            val handler = mView.handler ?: return
-            val action = intent.action
-            if (
+            if (handler == null) {
+                shadeLogger.d("VariableDateViewController received intent but handler was null")
+            } else if (
                     Intent.ACTION_TIME_TICK == action ||
                     Intent.ACTION_TIME_CHANGED == action ||
                     Intent.ACTION_TIMEZONE_CHANGED == action ||
                     Intent.ACTION_LOCALE_CHANGED == action
             ) {
-                if (
-                        Intent.ACTION_LOCALE_CHANGED == action ||
-                        Intent.ACTION_TIMEZONE_CHANGED == action
-                ) {
-                    // need to get a fresh date format
-                    handler.post { dateFormat = null }
-                }
                 handler.post(::updateClock)
             }
         }
@@ -211,12 +218,14 @@
     class Factory @Inject constructor(
         private val systemClock: SystemClock,
         private val broadcastDispatcher: BroadcastDispatcher,
+        private val shadeLogger: ShadeLogger,
         @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
     ) {
         fun create(view: VariableDateView): VariableDateViewController {
             return VariableDateViewController(
                     systemClock,
                     broadcastDispatcher,
+                    shadeLogger,
                     handler,
                     view
             )
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt
index 2325acf..f5decaa 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.unfold
 
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.FoldProvider
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener
 import java.util.concurrent.Executor
 
-class UnfoldProgressProvider(private val unfoldProgressProvider: UnfoldTransitionProgressProvider) :
+class UnfoldProgressProvider(private val unfoldProgressProvider: UnfoldTransitionProgressProvider,
+        private val foldProvider: FoldProvider) :
     ShellUnfoldProgressProvider {
 
     override fun addListener(executor: Executor, listener: UnfoldListener) {
@@ -39,5 +41,11 @@
                     executor.execute { listener.onStateChangeFinished() }
                 }
             })
+
+        foldProvider.registerCallback(object : FoldProvider.FoldCallback {
+            override fun onFoldUpdated(isFolded: Boolean) {
+                listener.onFoldStateChanged(isFolded)
+            }
+        }, executor)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 992b022..ed3eacd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -118,6 +118,7 @@
     @Singleton
     fun provideShellProgressProvider(
         config: UnfoldTransitionConfig,
+        foldProvider: FoldProvider,
         provider: Provider<Optional<UnfoldTransitionProgressProvider>>,
         @Named(UNFOLD_ONLY_PROVIDER)
         unfoldOnlyProvider: Provider<Optional<UnfoldTransitionProgressProvider>>
@@ -135,8 +136,9 @@
                 null
             }
 
-        return resultingProvider?.get()?.orElse(null)?.let(::UnfoldProgressProvider)
-            ?: ShellUnfoldProgressProvider.NO_PROVIDER
+        return resultingProvider?.get()?.orElse(null)?.let {
+            unfoldProgressProvider -> UnfoldProgressProvider(unfoldProgressProvider, foldProvider)
+        } ?: ShellUnfoldProgressProvider.NO_PROVIDER
     }
 
     @Provides
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 2b39354..d75405f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -20,6 +20,7 @@
 import android.view.MotionEvent
 import android.widget.SeekBar
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.settingslib.RestrictedLockUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -39,6 +40,7 @@
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.never
 import org.mockito.Mockito.notNull
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
@@ -64,6 +66,7 @@
     private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
     @Mock
     private lateinit var seekBar: SeekBar
+    private val uiEventLogger = UiEventLoggerFake()
     private var mFalsingManager: FalsingManagerFake = FalsingManagerFake()
 
     private lateinit var mController: BrightnessSliderController
@@ -75,7 +78,8 @@
         whenever(mirrorController.toggleSlider).thenReturn(mirror)
         whenever(motionEvent.copy()).thenReturn(motionEvent)
 
-        mController = BrightnessSliderController(brightnessSliderView, mFalsingManager)
+        mController =
+            BrightnessSliderController(brightnessSliderView, mFalsingManager, uiEventLogger)
         mController.init()
         mController.setOnChangedListener(listener)
     }
@@ -190,6 +194,7 @@
     @Test
     fun testSeekBarTrackingStarted() {
         whenever(brightnessSliderView.value).thenReturn(42)
+        val event = BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH
 
         mController.onViewAttached()
         mController.setMirrorControllerAndMirror(mirrorController)
@@ -200,11 +205,14 @@
         verify(listener).onChanged(eq(true), eq(42), eq(false))
         verify(mirrorController).showMirror()
         verify(mirrorController).setLocationAndSize(brightnessSliderView)
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        assertThat(uiEventLogger.eventId(0)).isEqualTo(event.id)
     }
 
     @Test
     fun testSeekBarTrackingStopped() {
         whenever(brightnessSliderView.value).thenReturn(23)
+        val event = BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH
 
         mController.onViewAttached()
         mController.setMirrorControllerAndMirror(mirrorController)
@@ -214,5 +222,7 @@
 
         verify(listener).onChanged(eq(false), eq(23), eq(true))
         verify(mirrorController).hideMirror()
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        assertThat(uiEventLogger.eventId(0)).isEqualTo(event.id)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
index 871a48c..12c1335 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -99,6 +100,7 @@
         controller = VariableDateViewController(
                 systemClock,
                 broadcastDispatcher,
+                mock(),
                 testableHandler,
                 view
         )
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index 247094f..ba43c8d 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -221,7 +221,7 @@
             }
             final AdiDeviceState deviceState = new AdiDeviceState(deviceType,
                     internalDeviceType, fields[1]);
-            deviceState.setHasHeadTracker(Integer.parseInt(fields[2]) == 1);
+            deviceState.setSAEnabled(Integer.parseInt(fields[2]) == 1);
             deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1);
             deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1);
             deviceState.setAudioDeviceCategory(audioDeviceCategory);
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index 356b301..54b34de 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -49,7 +49,8 @@
     // Upload the data every 50 attempts (average number of daily authentications).
     private static final int AUTHENTICATION_UPLOAD_INTERVAL = 50;
     // The maximum number of eligible biometric enrollment notification can be sent.
-    private static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
+    @VisibleForTesting
+    static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
 
     @NonNull private final Context mContext;
 
@@ -114,6 +115,10 @@
 
         AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
 
+        if (authenticationStats.getEnrollmentNotifications() >= MAXIMUM_ENROLLMENT_NOTIFICATIONS) {
+            return;
+        }
+
         authenticationStats.authenticate(authenticated);
 
         if (mPersisterInitialized) {
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
index 21e93a8..74e1410 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
@@ -21,6 +21,7 @@
 import android.content.SharedPreferences;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.Environment;
+import android.os.UserHandle;
 import android.util.Slog;
 
 import org.json.JSONException;
@@ -72,14 +73,16 @@
                 JSONObject frrStatsJson = new JSONObject(frrStats);
                 if (modality == BiometricsProtoEnums.MODALITY_FACE) {
                     authenticationStatsList.add(new AuthenticationStats(
-                            getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */),
+                            getIntValue(frrStatsJson, USER_ID,
+                                    UserHandle.USER_NULL /* defaultValue */),
                             getIntValue(frrStatsJson, FACE_ATTEMPTS),
                             getIntValue(frrStatsJson, FACE_REJECTIONS),
                             getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS),
                             modality));
                 } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
                     authenticationStatsList.add(new AuthenticationStats(
-                            getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */),
+                            getIntValue(frrStatsJson, USER_ID,
+                                    UserHandle.USER_NULL /* defaultValue */),
                             getIntValue(frrStatsJson, FINGERPRINT_ATTEMPTS),
                             getIntValue(frrStatsJson, FINGERPRINT_REJECTIONS),
                             getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS),
@@ -138,13 +141,11 @@
 
             // If there's existing frr stats in the file, we want to update the stats for the given
             // modality and keep the stats for other modalities.
-            if (frrStatJson != null) {
-                frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts,
-                        enrollmentNotifications, modality));
-            } else {
-                frrStatsSet.add(buildFrrStats(userId, totalAttempts, rejectedAttempts,
-                        enrollmentNotifications, modality));
+            if (frrStatJson == null) {
+                frrStatJson = new JSONObject().put(USER_ID, userId);
             }
+            frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts,
+                    enrollmentNotifications, modality));
 
             mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
 
@@ -177,29 +178,6 @@
         }
     }
 
-    // Build string for new user and new authentication stats.
-    private String buildFrrStats(int userId, int totalAttempts, int rejectedAttempts,
-            int enrollmentNotifications, int modality)
-            throws JSONException {
-        if (modality == BiometricsProtoEnums.MODALITY_FACE) {
-            return new JSONObject()
-                    .put(USER_ID, userId)
-                    .put(FACE_ATTEMPTS, totalAttempts)
-                    .put(FACE_REJECTIONS, rejectedAttempts)
-                    .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
-                    .toString();
-        } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
-            return new JSONObject()
-                    .put(USER_ID, userId)
-                    .put(FINGERPRINT_ATTEMPTS, totalAttempts)
-                    .put(FINGERPRINT_REJECTIONS, rejectedAttempts)
-                    .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
-                    .toString();
-        } else {
-            return "";
-        }
-    }
-
     private String getValue(JSONObject jsonObject, String key) throws JSONException {
         return jsonObject.has(key) ? jsonObject.getString(key) : "";
     }
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index d647757..6a36fbe 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -366,9 +366,10 @@
     }
 
     /**
-     * @return The current brightness recommendation calculated from the current conditions.
-     * @param brightnessEvent Event object to populate with details about why the specific
-     *                        brightness was chosen.
+     * @param brightnessEvent Holds details about how the brightness is calculated.
+     *
+     * @return The current automatic brightness recommended value. Populates brightnessEvent
+     *         parameters with details about how the brightness was calculated.
      */
     public float getAutomaticScreenBrightness(BrightnessEvent brightnessEvent) {
         if (brightnessEvent != null) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 486cd28..2464eb0 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -184,6 +184,7 @@
  *        <defaultRefreshRateInHbmSunlight>75</defaultRefreshRateInHbmSunlight>
  *        <lowerBlockingZoneConfigs>
  *          <defaultRefreshRate>75</defaultRefreshRate>
+ *          <refreshRateThermalThrottlingId>id_of_a_throttling_map</refreshRateThermalThrottlingId>
  *          <blockingZoneThreshold>
  *            <displayBrightnessPoint>
  *              <lux>50</lux>
@@ -722,6 +723,12 @@
     private float[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
     private float[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
 
+    /**
+     * Thermal throttling maps for the low and high blocking zones.
+     */
+    private String mLowBlockingZoneThermalMapId = null;
+    private String mHighBlockingZoneThermalMapId = null;
+
     private final HashMap<String, ThermalBrightnessThrottlingData>
             mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>();
 
@@ -1526,6 +1533,13 @@
     }
 
     /**
+     * @return The refresh rate thermal map for low blocking zone.
+     */
+    public SparseArray<SurfaceControl.RefreshRateRange> getLowBlockingZoneThermalMap() {
+        return getThermalRefreshRateThrottlingData(mLowBlockingZoneThermalMapId);
+    }
+
+    /**
      * @return An array of high display brightness thresholds. This, in combination with high
      * ambient brightness thresholds help define buckets in which the refresh rate switching is not
      * allowed.
@@ -1548,6 +1562,13 @@
     }
 
     /**
+     * @return The refresh rate thermal map for high blocking zone.
+     */
+    public SparseArray<SurfaceControl.RefreshRateRange> getHighBlockingZoneThermalMap() {
+        return getThermalRefreshRateThrottlingData(mHighBlockingZoneThermalMapId);
+    }
+
+    /**
      * @return A mapping from screen off brightness sensor readings to lux values. This estimates
      * the ambient lux when the screen is off to determine the initial brightness
      */
@@ -1664,6 +1685,8 @@
                 + ", mDefaultRefreshRateInHbmHdr= " + mDefaultRefreshRateInHbmHdr
                 + ", mDefaultRefreshRateInHbmSunlight= " + mDefaultRefreshRateInHbmSunlight
                 + ", mRefreshRateThrottlingMap= " + mRefreshRateThrottlingMap
+                + ", mLowBlockingZoneThermalMapId= " + mLowBlockingZoneThermalMapId
+                + ", mHighBlockingZoneThermalMapId= " + mHighBlockingZoneThermalMapId
                 + "\n"
                 + ", mLowDisplayBrightnessThresholds= "
                 + Arrays.toString(mLowDisplayBrightnessThresholds)
@@ -2114,9 +2137,13 @@
     }
 
     /**
-     * Loads the refresh rate configurations pertaining to the upper blocking zones.
+     * Loads the refresh rate configurations pertaining to the lower blocking zones.
      */
     private void loadLowerRefreshRateBlockingZones(BlockingZoneConfig lowerBlockingZoneConfig) {
+        if (lowerBlockingZoneConfig != null) {
+            mLowBlockingZoneThermalMapId =
+                    lowerBlockingZoneConfig.getRefreshRateThermalThrottlingId();
+        }
         loadLowerBlockingZoneDefaultRefreshRate(lowerBlockingZoneConfig);
         loadLowerBrightnessThresholds(lowerBlockingZoneConfig);
     }
@@ -2125,6 +2152,10 @@
      * Loads the refresh rate configurations pertaining to the upper blocking zones.
      */
     private void loadHigherRefreshRateBlockingZones(BlockingZoneConfig upperBlockingZoneConfig) {
+        if (upperBlockingZoneConfig != null) {
+            mHighBlockingZoneThermalMapId =
+                    upperBlockingZoneConfig.getRefreshRateThermalThrottlingId();
+        }
         loadHigherBlockingZoneDefaultRefreshRate(upperBlockingZoneConfig);
         loadHigherBrightnessThresholds(upperBlockingZoneConfig);
     }
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 82755b6..6079a32 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1540,6 +1540,21 @@
         private final Injector mInjector;
         private final Handler mHandler;
 
+        private final IThermalEventListener.Stub mThermalListener =
+                new IThermalEventListener.Stub() {
+                    @Override
+                    public void notifyThrottling(Temperature temp) {
+                        @Temperature.ThrottlingStatus int currentStatus = temp.getStatus();
+                        synchronized (mLock) {
+                            if (mThermalStatus != currentStatus) {
+                                mThermalStatus = currentStatus;
+                            }
+                            onBrightnessChangedLocked();
+                        }
+                    }
+                };
+        private boolean mThermalRegistered;
+
         // Enable light sensor only when mShouldObserveAmbientLowChange is true or
         // mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate
         // changeable and low power mode off. After initialization, these states will
@@ -1548,9 +1563,17 @@
         private boolean mRefreshRateChangeable = false;
         private boolean mLowPowerModeEnabled = false;
 
+        @Nullable
+        private SparseArray<RefreshRateRange> mLowZoneRefreshRateForThermals;
         private int mRefreshRateInLowZone;
+
+        @Nullable
+        private SparseArray<RefreshRateRange> mHighZoneRefreshRateForThermals;
         private int mRefreshRateInHighZone;
 
+        @GuardedBy("mLock")
+        private @Temperature.ThrottlingStatus int mThermalStatus = Temperature.THROTTLING_NONE;
+
         BrightnessObserver(Context context, Handler handler, Injector injector) {
             mContext = context;
             mHandler = handler;
@@ -1649,6 +1672,8 @@
                                 R.integer.config_defaultRefreshRateInZone)
                         : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
             }
+            mLowZoneRefreshRateForThermals = displayDeviceConfig == null ? null
+                    : displayDeviceConfig.getLowBlockingZoneThermalMap();
             mRefreshRateInLowZone = refreshRateInLowZone;
         }
 
@@ -1668,6 +1693,8 @@
                                 R.integer.config_fixedRefreshRateInHighZone)
                         : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate();
             }
+            mHighZoneRefreshRateForThermals = displayDeviceConfig == null ? null
+                    : displayDeviceConfig.getHighBlockingZoneThermalMap();
             mRefreshRateInHighZone = refreshRateInHighZone;
         }
 
@@ -2117,6 +2144,15 @@
             if (insideLowZone) {
                 refreshRateVote =
                         Vote.forPhysicalRefreshRates(mRefreshRateInLowZone, mRefreshRateInLowZone);
+                if (mLowZoneRefreshRateForThermals != null) {
+                    RefreshRateRange range = SkinThermalStatusObserver
+                            .findBestMatchingRefreshRateRange(mThermalStatus,
+                                    mLowZoneRefreshRateForThermals);
+                    if (range != null) {
+                        refreshRateVote =
+                                Vote.forPhysicalRefreshRates(range.min, range.max);
+                    }
+                }
                 refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
             }
 
@@ -2126,6 +2162,15 @@
                 refreshRateVote =
                         Vote.forPhysicalRefreshRates(mRefreshRateInHighZone,
                                 mRefreshRateInHighZone);
+                if (mHighZoneRefreshRateForThermals != null) {
+                    RefreshRateRange range = SkinThermalStatusObserver
+                            .findBestMatchingRefreshRateRange(mThermalStatus,
+                                    mHighZoneRefreshRateForThermals);
+                    if (range != null) {
+                        refreshRateVote =
+                                Vote.forPhysicalRefreshRates(range.min, range.max);
+                    }
+                }
                 refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
             }
 
@@ -2184,13 +2229,25 @@
                         + mRefreshRateChangeable);
             }
 
+            boolean registerForThermals = false;
             if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
                      && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
                 registerLightSensor();
-
+                registerForThermals = mLowZoneRefreshRateForThermals != null
+                        || mHighZoneRefreshRateForThermals != null;
             } else {
                 unregisterSensorListener();
             }
+
+            if (registerForThermals && !mThermalRegistered) {
+                mThermalRegistered = mInjector.registerThermalServiceListener(mThermalListener);
+            } else if (!registerForThermals && mThermalRegistered) {
+                mInjector.unregisterThermalServiceListener(mThermalListener);
+                mThermalRegistered = false;
+                synchronized (mLock) {
+                    mThermalStatus = Temperature.THROTTLING_NONE; // reset
+                }
+            }
         }
 
         private void registerLightSensor() {
@@ -2823,6 +2880,7 @@
         boolean isDozeState(Display d);
 
         boolean registerThermalServiceListener(IThermalEventListener listener);
+        void unregisterThermalServiceListener(IThermalEventListener listener);
 
         boolean supportsFrameRateOverride();
     }
@@ -2918,6 +2976,19 @@
         }
 
         @Override
+        public void unregisterThermalServiceListener(IThermalEventListener listener) {
+            IThermalService thermalService = getThermalService();
+            if (thermalService == null) {
+                Slog.w(TAG, "Could not unregister thermal status. Service not available");
+            }
+            try {
+                thermalService.unregisterThermalEventListener(listener);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to unregister thermal status listener", e);
+            }
+        }
+
+        @Override
         public boolean supportsFrameRateOverride() {
             return SurfaceFlingerProperties.enable_frame_rate_override().orElse(true);
         }
diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
index 58e1550..b29cda8 100644
--- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
+++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
@@ -64,6 +64,20 @@
         mHandler = handler;
     }
 
+    @Nullable
+    public static SurfaceControl.RefreshRateRange findBestMatchingRefreshRateRange(
+            @Temperature.ThrottlingStatus int currentStatus,
+            SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) {
+        SurfaceControl.RefreshRateRange foundRange = null;
+        for (int status = currentStatus; status >= 0; status--) {
+            foundRange = throttlingMap.get(status);
+            if (foundRange != null) {
+                break;
+            }
+        }
+        return foundRange;
+    }
+
     void observe() {
         // if failed to register thermal service listener, don't register display listener
         if (!mInjector.registerThermalServiceListener(this)) {
@@ -228,20 +242,6 @@
         }
     }
 
-    @Nullable
-    private SurfaceControl.RefreshRateRange findBestMatchingRefreshRateRange(
-            @Temperature.ThrottlingStatus int currentStatus,
-            SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) {
-        SurfaceControl.RefreshRateRange foundRange = null;
-        for (int status = currentStatus; status >= 0; status--) {
-            foundRange = throttlingMap.get(status);
-            if (foundRange != null) {
-                break;
-            }
-        }
-        return foundRange;
-    }
-
     private void fallbackReportThrottlingIfNeeded(int displayId,
             @Temperature.ThrottlingStatus int currentStatus) {
         Vote vote = null;
diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
index d238dae..2ede56d 100644
--- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
+++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
@@ -67,7 +67,7 @@
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
-        t.setLayer(mInputSurface, Integer.MAX_VALUE);
+        t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_GESTURE_MONITOR);
         t.setPosition(mInputSurface, 0, 0);
         t.setCrop(mInputSurface, null /* crop to parent surface */);
         t.show(mInputSurface);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 5c80291..ff69719 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -381,6 +381,17 @@
     public static final int SW_CAMERA_LENS_COVER_BIT = 1 << SW_CAMERA_LENS_COVER;
     public static final int SW_MUTE_DEVICE_BIT = 1 << SW_MUTE_DEVICE;
 
+    // The following are layer numbers used for z-ordering the input overlay layers on the display.
+    // This is used for ordering layers inside {@code DisplayContent#getInputOverlayLayer()}.
+    //
+    // The layer where gesture monitors are added.
+    public static final int INPUT_OVERLAY_LAYER_GESTURE_MONITOR = 1;
+    // Place the handwriting layer above gesture monitors so that styluses cannot trigger
+    // system gestures (e.g. navigation bar, edge-back, etc) while there is an active
+    // handwriting session.
+    public static final int INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE = 2;
+
+
     private final String mVelocityTrackerStrategy;
 
     /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index 0c889c2..7726f40 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -27,16 +27,13 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
+import com.android.server.input.InputManagerService;
+
 final class HandwritingEventReceiverSurface {
 
     public static final String TAG = HandwritingEventReceiverSurface.class.getSimpleName();
     static final boolean DEBUG = HandwritingModeController.DEBUG;
 
-    // Place the layer at the highest layer so stylus cannot trigger gesture monitors
-    // (e.g. navigation bar, edge-back, etc) while handwriting is ongoing.
-    // TODO(b/217538817): Specify the ordering in WM by usage.
-    private static final int HANDWRITING_SURFACE_LAYER = Integer.MAX_VALUE;
-
     private final InputWindowHandle mWindowHandle;
     private final InputChannel mClientChannel;
     private final SurfaceControl mInputSurface;
@@ -68,7 +65,7 @@
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
-        t.setLayer(mInputSurface, HANDWRITING_SURFACE_LAYER);
+        t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE);
         t.setPosition(mInputSurface, 0, 0);
         t.setCrop(mInputSurface, null /* crop to parent surface */);
         t.show(mInputSurface);
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 9185a00..4084462 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -1062,6 +1062,14 @@
                         != 0) {
                     return;
                 }
+
+                if (pi != null && pi.isActivity()) {
+                    Log.w(
+                            TAG,
+                            "Ignoring invalid media button receiver targeting an activity: " + pi);
+                    return;
+                }
+
                 mMediaButtonReceiverHolder =
                         MediaButtonReceiverHolder.create(mUserId, pi, mPackageName);
                 mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 0250475..0a2bbd4 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -455,13 +455,11 @@
      * or seamless transformation in a rotated display.
      */
     boolean shouldFreezeInsetsPosition(WindowState w) {
-        if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) {
-            // Expect a screenshot layer has covered the screen, so it is fine to let client side
-            // insets animation runner update the position directly.
-            return false;
-        }
-        return mTransitionOp != OP_LEGACY && !mIsStartTransactionCommitted
-                && isTargetToken(w.mToken);
+        // Non-change transition (OP_APP_SWITCH) and METHOD_BLAST don't use screenshot so the
+        // insets should keep original position before the start transaction is applied.
+        return mTransitionOp != OP_LEGACY && (mTransitionOp == OP_APP_SWITCH
+                || TransitionController.SYNC_METHOD == BLASTSyncEngine.METHOD_BLAST)
+                && !mIsStartTransactionCommitted && isTargetToken(w.mToken);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4cb4fe2..4a40395 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -328,6 +328,12 @@
      */
     private SurfaceControl mOverlayLayer;
 
+    /**
+     * A SurfaceControl that contains input overlays used for cases where we need to receive input
+     * over the entire display.
+     */
+    private SurfaceControl mInputOverlayLayer;
+
     /** A surfaceControl specifically for accessibility overlays. */
     private SurfaceControl mA11yOverlayLayer;
 
@@ -1329,6 +1335,12 @@
             transaction.reparent(mOverlayLayer, mSurfaceControl);
         }
 
+        if (mInputOverlayLayer == null) {
+            mInputOverlayLayer = b.setName("Input Overlays").setParent(mSurfaceControl).build();
+        } else {
+            transaction.reparent(mInputOverlayLayer, mSurfaceControl);
+        }
+
         if (mA11yOverlayLayer == null) {
             mA11yOverlayLayer =
                     b.setName("Accessibility Overlays").setParent(mSurfaceControl).build();
@@ -1342,7 +1354,9 @@
                 .show(mSurfaceControl)
                 .setLayer(mOverlayLayer, Integer.MAX_VALUE)
                 .show(mOverlayLayer)
-                .setLayer(mA11yOverlayLayer, Integer.MAX_VALUE - 1)
+                .setLayer(mInputOverlayLayer, Integer.MAX_VALUE - 1)
+                .show(mInputOverlayLayer)
+                .setLayer(mA11yOverlayLayer, Integer.MAX_VALUE - 2)
                 .show(mA11yOverlayLayer);
     }
 
@@ -3353,6 +3367,7 @@
             // -> this DisplayContent.
             setRemoteInsetsController(null);
             mOverlayLayer.release();
+            mInputOverlayLayer.release();
             mA11yOverlayLayer.release();
             mWindowingLayer.release();
             mInputMonitor.onDisplayRemoved();
@@ -5703,6 +5718,10 @@
         return mOverlayLayer;
     }
 
+    SurfaceControl getInputOverlayLayer() {
+        return mInputOverlayLayer;
+    }
+
     SurfaceControl getA11yOverlayLayer() {
         return mA11yOverlayLayer;
     }
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 73fdfe0..8cf4713 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -275,11 +275,17 @@
                         + " - DisplayContent not found.");
                 return null;
             }
+            final SurfaceControl inputOverlay = dc.getInputOverlayLayer();
+            if (inputOverlay == null) {
+                Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId
+                        + " - Input overlay layer is not initialized.");
+                return null;
+            }
             return mService.makeSurfaceBuilder(dc.getSession())
                     .setContainerLayer()
                     .setName(name)
                     .setCallsite("createSurfaceForGestureMonitor")
-                    .setParent(dc.getSurfaceControl())
+                    .setParent(inputOverlay)
                     .build();
         }
     }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 83fd725..098f32b 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -225,7 +225,6 @@
                 final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
                 dc.requestTransitionAndLegacyPrepare(
                         TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
-                dc.mWallpaperController.showWallpaperInTransition(false /* showHome */);
                 mWindowManager.executeAppTransition();
             }
         }
@@ -282,8 +281,6 @@
                     TRANSIT_TO_BACK, transitFlags, null /* trigger */, dc);
             updateKeyguardSleepToken();
 
-            // Make the home wallpaper visible
-            dc.mWallpaperController.showWallpaperInTransition(true /* showHome */);
             // Some stack visibility might change (e.g. docked stack)
             mRootWindowContainer.resumeFocusedTasksTopActivities();
             mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 5b466a0..3eabbe7 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1147,6 +1147,7 @@
             Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
         } else if (!animatingState && mAnimatingState) {
             t.setEarlyWakeupEnd();
+            mAtm.mWindowManager.requestTraversal();
             mSnapshotController.setPause(false);
             mAnimatingState = false;
             Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 00bedcd..9c08c74 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -314,31 +314,6 @@
                         || !mWallpaperTarget.mActivityRecord.isWaitingForTransitionStart());
     }
 
-    /**
-     * Make one wallpaper visible, according to {@attr showHome}.
-     * This is called during the keyguard unlocking transition
-     * (see {@link KeyguardController#keyguardGoingAway(int, int)}),
-     * or when a keyguard unlock is cancelled (see {@link KeyguardController})
-     */
-    public void showWallpaperInTransition(boolean showHome) {
-        updateWallpaperWindowsTarget(mFindResults);
-
-        if (!mFindResults.hasTopShowWhenLockedWallpaper()) {
-            Slog.w(TAG, "There is no wallpaper for the lock screen");
-            return;
-        }
-        WindowState hideWhenLocked = mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper;
-        WindowState showWhenLocked = mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper;
-        if (!mFindResults.hasTopHideWhenLockedWallpaper()) {
-            // Shared wallpaper, ensure its visibility
-            showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindows(true);
-        } else {
-            // Separate lock and home wallpapers: show the correct wallpaper in transition
-            hideWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(showHome);
-            showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(!showHome);
-        }
-    }
-
     void hideDeferredWallpapersIfNeededLegacy() {
         for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(i);
@@ -840,10 +815,7 @@
             }
         }
 
-        if (!mDisplayContent.isKeyguardGoingAway() || !mIsLockscreenLiveWallpaperEnabled) {
-            // When keyguard goes away, KeyguardController handles the visibility
-            updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked());
-        }
+        updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked());
 
         if (DEBUG_WALLPAPER) {
             Slog.v(TAG, "adjustWallpaperWindows: wallpaper visibility " + visible
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 8e0ad0d..c7fd147 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -128,20 +128,6 @@
         }
     }
 
-    /**
-     * Update the visibility of the token to {@param visible}. If a transition will collect the
-     * wallpaper, then the visibility will be committed during the execution of the transition.
-     *
-     * waitingToShow is reset at the beginning of the transition:
-     * {@link Transition#onTransactionReady(int, SurfaceControl.Transaction)}
-     */
-    void updateWallpaperWindowsInTransition(boolean visible) {
-        if (mTransitionController.isCollecting() && mVisibleRequested != visible) {
-            waitingToShow = true;
-        }
-        updateWallpaperWindows(visible);
-    }
-
     void updateWallpaperWindows(boolean visible) {
         if (mVisibleRequested != visible) {
             ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
@@ -212,12 +198,9 @@
         commitVisibility(visible);
     }
 
-    /**
-     * Commits the visibility of this token. This will directly update the visibility unless the
-     * wallpaper is in a transition.
-     */
+    /** Commits the visibility of this token. This will directly update the visibility. */
     void commitVisibility(boolean visible) {
-        if (visible == isVisible() || waitingToShow) return;
+        if (visible == isVisible()) return;
 
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                 "commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3a19a3b..b20be55 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8311,12 +8311,18 @@
                             + displayId + " - DisplayContent not found.");
                     return null;
                 }
-                //TODO (b/210039666): Use a method like add/removeDisplayOverlay if available.
+                final SurfaceControl inputOverlay = dc.getInputOverlayLayer();
+                if (inputOverlay == null) {
+                    Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId
+                            + " - Input overlay layer is not initialized.");
+                    return null;
+                }
+                // TODO(b/210039666): Use a method like add/removeDisplayOverlay if available.
                 return makeSurfaceBuilder(dc.getSession())
                         .setContainerLayer()
                         .setName("IME Handwriting Surface")
                         .setCallsite("getHandwritingSurfaceForDisplay")
-                        .setParent(dc.getSurfaceControl())
+                        .setParent(inputOverlay)
                         .build();
             }
         }
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 7104a80..b63154d 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -579,6 +579,10 @@
                     minOccurs="1" maxOccurs="1">
             <xs:annotation name="final"/>
         </xs:element>
+        <xs:element type ="xs:string" name="refreshRateThermalThrottlingId">
+            <xs:annotation name="nullable"/>
+            <xs:annotation name="final"/>
+        </xs:element>
         <xs:element name="blockingZoneThreshold" type="blockingZoneThreshold"
                     minOccurs="1" maxOccurs="1">
             <xs:annotation name="final"/>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 507c9dc..426fcb6 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -17,8 +17,10 @@
     ctor public BlockingZoneConfig();
     method public final com.android.server.display.config.BlockingZoneThreshold getBlockingZoneThreshold();
     method public final java.math.BigInteger getDefaultRefreshRate();
+    method @Nullable public final String getRefreshRateThermalThrottlingId();
     method public final void setBlockingZoneThreshold(com.android.server.display.config.BlockingZoneThreshold);
     method public final void setDefaultRefreshRate(java.math.BigInteger);
+    method public final void setRefreshRateThermalThrottlingId(@Nullable String);
   }
 
   public class BlockingZoneThreshold {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index da7a6a1..4752f81 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -268,6 +268,14 @@
                 mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.25f),
                 SMALL_DELTA);
 
+        // Low/High zone thermal maps
+        assertEquals(new SurfaceControl.RefreshRateRange(30, 40),
+                mDisplayDeviceConfig.getLowBlockingZoneThermalMap()
+                .get(Temperature.THROTTLING_CRITICAL));
+        assertEquals(new SurfaceControl.RefreshRateRange(40, 60),
+                mDisplayDeviceConfig.getHighBlockingZoneThermalMap()
+                .get(Temperature.THROTTLING_EMERGENCY));
+
         // Todo: Add asserts for DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
     }
@@ -530,6 +538,24 @@
                + "        </refreshRateRange>\n"
                + "    </refreshRateThrottlingPoint>\n"
                + "</refreshRateThrottlingMap>\n"
+               + "<refreshRateThrottlingMap id=\"thermalLow\">\n"
+               + "    <refreshRateThrottlingPoint>\n"
+               + "        <thermalStatus>critical</thermalStatus>\n"
+               + "        <refreshRateRange>\n"
+               + "            <minimum>30</minimum>\n"
+               + "            <maximum>40</maximum>\n"
+               + "        </refreshRateRange>\n"
+               + "    </refreshRateThrottlingPoint>\n"
+               + "</refreshRateThrottlingMap>\n"
+               + "<refreshRateThrottlingMap id=\"thermalHigh\">\n"
+               + "    <refreshRateThrottlingPoint>\n"
+               + "        <thermalStatus>emergency</thermalStatus>\n"
+               + "        <refreshRateRange>\n"
+               + "            <minimum>40</minimum>\n"
+               + "            <maximum>60</maximum>\n"
+               + "        </refreshRateRange>\n"
+               + "    </refreshRateThrottlingPoint>\n"
+               + "</refreshRateThrottlingMap>\n"
                + "<refreshRateThrottlingMap id=\"test\">\n"
                + "    <refreshRateThrottlingPoint>\n"
                + "        <thermalStatus>emergency</thermalStatus>\n"
@@ -822,6 +848,8 @@
                 +       "<defaultRefreshRateInHbmSunlight>83</defaultRefreshRateInHbmSunlight>\n"
                 +       "<lowerBlockingZoneConfigs>\n"
                 +           "<defaultRefreshRate>75</defaultRefreshRate>\n"
+                +           "<refreshRateThermalThrottlingId>thermalLow"
+                +           "</refreshRateThermalThrottlingId>\n"
                 +           "<blockingZoneThreshold>\n"
                 +               "<displayBrightnessPoint>\n"
                 +                   "<lux>50</lux>\n"
@@ -843,6 +871,8 @@
                 +       "</lowerBlockingZoneConfigs>\n"
                 +       "<higherBlockingZoneConfigs>\n"
                 +           "<defaultRefreshRate>90</defaultRefreshRate>\n"
+                +           "<refreshRateThermalThrottlingId>thermalHigh"
+                +           "</refreshRateThermalThrottlingId>\n"
                 +           "<blockingZoneThreshold>\n"
                 +               "<displayBrightnessPoint>\n"
                 +                   "<lux>70</lux>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 15f13cd..89a1e13 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -1100,6 +1101,196 @@
     }
 
     @Test
+    public void testLockFpsForHighZoneWithThermalCondition() throws Exception {
+        // First, configure brightness zones or DMD won't register for sensor data.
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setHighDisplayBrightnessThresholds(new int[] { 200 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 120.f}, 0);
+        setPeakRefreshRate(120 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(120);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        // Set the thresholds for High Zone
+        DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
+        when(ddcMock.getDefaultHighBlockingZoneRefreshRate()).thenReturn(90);
+        when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new float[] { 200 });
+        when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new float[] { 8000 });
+        when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90);
+        when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new float[] {});
+        when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new float[] {});
+
+        // Set the thermal condition for refresh rate range
+        when(ddcMock.getHighBlockingZoneThermalMap()).thenReturn(
+                new SparseArray<RefreshRateRange>() {{
+                    put(Temperature.THROTTLING_CRITICAL, new RefreshRateRange(60, 60));
+                }}
+        );
+        director.defaultDisplayDeviceUpdated(ddcMock); // set the ddc
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        // Get the display listener so that we can send it new brightness events
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        // Get the sensor listener so that we can give it new light sensor events
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+        SensorEventListener sensorListener = listenerCaptor.getValue();
+
+        // Get the thermal listener so that we can give it new thermal conditions
+        ArgumentCaptor<IThermalEventListener> thermalListenerCaptor =
+                ArgumentCaptor.forClass(IThermalEventListener.class);
+        verify(mInjector, atLeastOnce()).registerThermalServiceListener(
+                thermalListenerCaptor.capture());
+        List<IThermalEventListener> thermalListeners = thermalListenerCaptor.getAllValues();
+
+        setBrightness(100, 100, displayListener);
+        // Sensor reads 2000 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertThat(vote).isNull();
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNull();
+
+        // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+        // parameter to the necessary threshold
+        setBrightness(255, 255, displayListener);
+        // Sensor reads 9000 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNotNull();
+        assertThat(vote.disableRefreshRateSwitching).isTrue();
+
+        // Set critical and check new refresh rate
+        Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL);
+        for (var listener : thermalListeners) {
+            listener.notifyThrottling(temp);
+        }
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/);
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNotNull();
+        assertThat(vote.disableRefreshRateSwitching).isTrue();
+    }
+
+    @Test
+    public void testLockFpsForLowZoneWithThermalCondition() throws Exception {
+        // First, configure brightness zones or DMD won't register for sensor data.
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setHighDisplayBrightnessThresholds(new int[] { 200 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 120.f}, 0);
+        setPeakRefreshRate(120 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(120);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        // Set the thresholds for Low Zone
+        DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
+        when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90);
+        when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new float[] { 200 });
+        when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new float[] { 8000 });
+        when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90);
+        when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new float[] { 10 });
+        when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new float[] { 10 });
+
+        // Set the thermal condition for refresh rate range
+        when(ddcMock.getLowBlockingZoneThermalMap()).thenReturn(
+                new SparseArray<RefreshRateRange>() {{
+                    put(Temperature.THROTTLING_CRITICAL, new RefreshRateRange(60, 60));
+                }}
+        );
+        director.defaultDisplayDeviceUpdated(ddcMock); // set the ddc
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        // Get the display listener so that we can send it new brightness events
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        // Get the sensor listener so that we can give it new light sensor events
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+        SensorEventListener sensorListener = listenerCaptor.getValue();
+
+        // Get the thermal listener so that we can give it new thermal conditions
+        ArgumentCaptor<IThermalEventListener> thermalListenerCaptor =
+                ArgumentCaptor.forClass(IThermalEventListener.class);
+        verify(mInjector, atLeastOnce()).registerThermalServiceListener(
+                thermalListenerCaptor.capture());
+        List<IThermalEventListener> thermalListeners = thermalListenerCaptor.getAllValues();
+
+        setBrightness(100, 100, displayListener);
+        // Sensor reads 2000 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertThat(vote).isNull();
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNull();
+
+        // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+        // parameter to the necessary threshold
+        setBrightness(5, 5, displayListener);
+        // Sensor reads 9 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9));
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNotNull();
+        assertThat(vote.disableRefreshRateSwitching).isTrue();
+
+        // Set critical and check new refresh rate
+        Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL);
+        for (var listener : thermalListeners) {
+            listener.notifyThrottling(temp);
+        }
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/);
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNotNull();
+        assertThat(vote.disableRefreshRateSwitching).isTrue();
+    }
+
+    @Test
     public void testSensorRegistration() {
         // First, configure brightness zones or DMD won't register for sensor data.
         final FakeDeviceConfig config = mInjector.getDeviceConfig();
@@ -2902,6 +3093,10 @@
         }
 
         @Override
+        public void unregisterThermalServiceListener(IThermalEventListener listener) {
+        }
+
+        @Override
         public boolean supportsFrameRateOverride() {
             return true;
         }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index 746fb53..64e776e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics;
 
+import static com.android.server.biometrics.AuthenticationStatsCollector.MAXIMUM_ENROLLMENT_NOTIFICATIONS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -44,9 +46,11 @@
 import com.android.server.biometrics.sensors.BiometricNotification;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.io.File;
 
@@ -54,6 +58,9 @@
 @SmallTest
 public class AuthenticationStatsCollectorTest {
 
+    @Rule
+    public MockitoRule mockitoRule = MockitoJUnit.rule();
+
     private AuthenticationStatsCollector mAuthenticationStatsCollector;
     private static final float FRR_THRESHOLD = 0.2f;
     private static final int USER_ID_1 = 1;
@@ -75,7 +82,6 @@
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
 
         when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getFraction(eq(R.fraction.config_biometricNotificationFrrThreshold),
@@ -130,6 +136,33 @@
         assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
     }
 
+    /**
+     * Our current use case does not need the counters to update after the notification
+     * limit is reached. If we need these counters to continue counting in the future,
+     * a separate privacy review must be done.
+     */
+    @Test
+    public void authenticate_notificationExceeded_mapMustNotBeUpdated() {
+
+        mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+                new AuthenticationStats(USER_ID_1, 400 /* totalAttempts */,
+                        40 /* rejectedAttempts */,
+                        MAXIMUM_ENROLLMENT_NOTIFICATIONS /* enrollmentNotifications */,
+                        0 /* modality */));
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+        AuthenticationStats authenticationStats =
+                mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+
+        assertThat(authenticationStats.getUserId()).isEqualTo(USER_ID_1);
+        // Assert that counters haven't been updated.
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(400);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(40);
+        assertThat(authenticationStats.getEnrollmentNotifications())
+                .isEqualTo(MAXIMUM_ENROLLMENT_NOTIFICATIONS);
+    }
+
     @Test
     public void authenticate_frrNotExceeded_notificationNotExceeded_shouldNotSendNotification() {
 
@@ -156,7 +189,8 @@
 
         mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
                 new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
-                        400 /* rejectedAttempts */, 2 /* enrollmentNotifications */,
+                        400 /* rejectedAttempts */,
+                        MAXIMUM_ENROLLMENT_NOTIFICATIONS /* enrollmentNotifications */,
                         0 /* modality */));
 
         mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
@@ -164,12 +198,12 @@
         // Assert that no notification should be sent.
         verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
         verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
-        // Assert that data has been reset.
+        // Assert that data hasn't been reset.
         AuthenticationStats authenticationStats = mAuthenticationStatsCollector
                 .getAuthenticationStatsForUser(USER_ID_1);
-        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
-        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
-        assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(500);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(400);
+        assertThat(authenticationStats.getFrr()).isWithin(0f).of(0.8f);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 7544fda..be4ef6f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1197,7 +1197,7 @@
         final AsyncRotationController asyncRotationController =
                 mDisplayContent.getAsyncRotationController();
         assertNotNull(asyncRotationController);
-        assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
+        assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar));
         assertTrue(app.getTask().inTransition());
 
         player.start();
@@ -1222,6 +1222,7 @@
         assertFalse(asyncRotationController.isTargetToken(navBar.mToken));
         navBar.finishDrawing(null /* postDrawTransaction */, Integer.MAX_VALUE);
         assertTrue(asyncRotationController.isTargetToken(navBar.mToken));
+        assertTrue(asyncRotationController.shouldFreezeInsetsPosition(navBar));
 
         player.startTransition();
         // Non-app windows should not be collected.
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index 807f0c6..e60d8ef 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -23,6 +23,7 @@
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.requestFocusAndVerify;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
@@ -225,7 +226,7 @@
         Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity firstActivity = TestActivity.start(intent1);
         // Request view focus after app starts
-        mInstrumentation.runOnMainSync(firstActivity::requestFocus);
+        requestFocusAndVerify(firstActivity);
 
         Intent intent2 =
                 createIntent(
@@ -252,7 +253,7 @@
         Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent1);
         // Request view focus after app starts
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
         // Create second TestActivity
         Intent intent2 =
@@ -284,7 +285,7 @@
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request view focus after app starts
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
         // Find the editText and click it
         UiObject2 editTextUiObject =
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
index 320daee..2ac25f2 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
@@ -23,6 +23,7 @@
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.requestFocusAndVerify;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
@@ -96,7 +97,7 @@
         UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         eventually(
                 () ->
-                        assertWithMessage("Display rotation should be updated.")
+                        assertWithMessage("Display rotation should have been updated")
                                 .that(uiDevice.getDisplayRotation())
                                 .isEqualTo(mIsPortrait ? 0 : 1),
                 TIMEOUT);
@@ -104,7 +105,7 @@
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
             // TODO(b/291752364): Remove the explicit focus request once the issue with view focus
             //  change between fullscreen IME and actual editText is fixed.
-            callOnMainSync(editText::requestFocus);
+            requestFocusAndVerify(activity);
             verifyWindowAndViewFocus(editText, true, true);
             callOnMainSync(activity::showImeWithInputMethodManager);
             waitOnMainUntilImeIsShown(editText);
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index 5c02124..5368025 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -29,6 +29,7 @@
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.isImeShown;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.requestFocusAndVerify;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
@@ -38,6 +39,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
 import android.app.Instrumentation;
 import android.content.Intent;
 import android.os.Build;
@@ -96,7 +100,8 @@
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
+
         // Test only once if window flags set to save time.
         int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
         for (int i = 0; i < iterNum; i++) {
@@ -106,21 +111,19 @@
             verifyShowBehavior(activity);
 
             callOnMainSync(activity::hideImeWithInputMethodManager);
-
             verifyHideBehavior(activity);
         }
     }
 
     @Test
     public void testShowHideWithInputMethodManager_waitingAnimationEnd() {
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
+
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
         activity.enableAnimationMonitoring();
         EditText editText = activity.getEditText();
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
@@ -128,12 +131,12 @@
             Log.i(TAG, msgPrefix + "start");
             callOnMainSync(activity::showImeWithInputMethodManager);
             waitOnMainUntil(
-                    msgPrefix + "IME should be visible",
+                    msgPrefix + "IME should have been shown",
                     () -> !activity.isAnimating() && isImeShown(editText));
 
             callOnMainSync(activity::hideImeWithInputMethodManager);
             waitOnMainUntil(
-                    msgPrefix + "IME should be hidden",
+                    msgPrefix + "IME should have been hidden",
                     () -> !activity.isAnimating() && !isImeShown(editText));
         }
     }
@@ -141,13 +144,13 @@
     @Test
     public void testShowHideWithInputMethodManager_intervalAfterHide() {
         // Regression test for b/221483132
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
+
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
+        requestFocusAndVerify(activity);
+
         // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
         List<Integer> intervals = new ArrayList<>();
         for (int i = 10; i < 100; i += 10) intervals.add(i);
@@ -165,14 +168,12 @@
 
     @Test
     public void testShowHideWithInputMethodManager_inSameFrame() {
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
         // hidden -> show -> hide
         mInstrumentation.runOnMainSync(
                 () -> {
@@ -256,13 +257,12 @@
 
     @Test
     public void testShowHideWithWindowInsetsController_waitingVisibilityChange() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
+
         // Test only once if window flags set to save time.
         int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
         for (int i = 0; i < iterNum; i++) {
@@ -277,17 +277,13 @@
 
     @Test
     public void testShowHideWithWindowInsetsController_waitingAnimationEnd() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
         activity.enableAnimationMonitoring();
         EditText editText = activity.getEditText();
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
@@ -295,29 +291,25 @@
             Log.i(TAG, msgPrefix + "start");
             mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
             waitOnMainUntil(
-                    msgPrefix + "IME should be visible",
+                    msgPrefix + "IME should have been shown",
                     () -> !activity.isAnimating() && isImeShown(editText));
 
             mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
             waitOnMainUntil(
-                    msgPrefix + "IME should be hidden",
+                    msgPrefix + "IME should have been hidden",
                     () -> !activity.isAnimating() && !isImeShown(editText));
         }
     }
 
     @Test
     public void testShowHideWithWindowInsetsController_intervalAfterHide() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
         // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
         List<Integer> intervals = new ArrayList<>();
         for (int i = 10; i < 100; i += 10) intervals.add(i);
@@ -335,17 +327,13 @@
 
     @Test
     public void testShowHideWithWindowInsetsController_inSameFrame() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
         // hidden -> show -> hide
         mInstrumentation.runOnMainSync(
                 () -> {
@@ -377,9 +365,7 @@
 
     @Test
     public void testShowWithWindowInsetsController_onCreate_requestFocus() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
         // Show with InputMethodManager at onCreate()
         Intent intent =
                 createIntent(
@@ -394,10 +380,8 @@
 
     @Test
     public void testShowWithWindowInsetsController_onCreate_notRequestFocus() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
-        // Show and hide with InputMethodManager at onCreate()
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
+        // Show and hide with WindowInsetsController at onCreate()
         Intent intent =
                 createIntent(
                         mWindowFocusFlags,
@@ -411,10 +395,8 @@
 
     @Test
     public void testShowWithWindowInsetsController_afterStart_notRequestFocus() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
-        // Show and hide with InputMethodManager at onCreate()
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
+        // Show and hide with WindowInsetsController at onCreate()
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
@@ -425,7 +407,8 @@
 
     /**
      * Test IME hidden by calling show and hide IME consecutively with
-     * {@link android.view.WindowInsetsController} APIs in {@link android.app.Activity#onCreate}.
+     * {@link android.view.WindowInsetsController} APIs in
+     * {@link android.app.Activity#onCreate}.
      *
      * <p> Note for developers: Use {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_UNCHANGED}
      * window flag to avoid some softInputMode visibility flags may take presence over
@@ -436,13 +419,11 @@
      */
     @Test
     public void testHideWithWindowInsetsController_onCreate_requestFocus() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
         if (mSoftInputFlags != SOFT_INPUT_STATE_UNCHANGED) {
             return;
         }
-        // Show and hide with InputMethodManager at onCreate()
+        // Show and hide with WindowInsetsController at onCreate()
         Intent intent =
                 createIntent(
                         mWindowFocusFlags,
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index 12556bc..c0c60ef 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -40,6 +40,7 @@
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -149,6 +150,14 @@
     }
 
     /**
+     * Requests EditText view focus on the main thread, and assert this returns {@code true}.
+     */
+    public static void requestFocusAndVerify(TestActivity activity) {
+        boolean result = callOnMainSync(activity::requestFocus);
+        assertWithMessage("View focus request should have succeeded").that(result).isTrue();
+    }
+
+    /**
      * Waits until {@code pred} returns true, or throws on timeout.
      *
      * <p>The given {@code pred} will be called on the main thread.
@@ -161,7 +170,7 @@
     public static void waitOnMainUntilImeIsShown(View view) {
         eventually(
                 () ->
-                        assertWithMessage("IME should be shown")
+                        assertWithMessage("IME should have been shown")
                                 .that(callOnMainSync(() -> isImeShown(view)))
                                 .isTrue(),
                 TIMEOUT);
@@ -171,27 +180,28 @@
     public static void waitOnMainUntilImeIsHidden(View view) {
         eventually(
                 () ->
-                        assertWithMessage("IME should be hidden")
+                        assertWithMessage("IME should have been hidden")
                                 .that(callOnMainSync(() -> isImeShown(view)))
                                 .isFalse(),
                 TIMEOUT);
     }
 
-    /** Waits until window get focus, or throws on timeout. */
+    /** Waits until window gains focus, or throws on timeout. */
     public static void waitOnMainUntilWindowGainsFocus(View view) {
         eventually(
                 () ->
-                        assertWithMessage("Window should gain focus")
+                        assertWithMessage(
+                                "Window should have gained focus; value of hasWindowFocus:")
                                 .that(callOnMainSync(view::hasWindowFocus))
                                 .isTrue(),
                 TIMEOUT);
     }
 
-    /** Waits until view get focus, or throws on timeout. */
+    /** Waits until view gains focus, or throws on timeout. */
     public static void waitOnMainUntilViewGainsFocus(View view) {
         eventually(
                 () ->
-                        assertWithMessage("View should gain focus")
+                        assertWithMessage("View should have gained focus; value of hasFocus:")
                                 .that(callOnMainSync(view::hasFocus))
                                 .isTrue(),
                 TIMEOUT);
@@ -201,7 +211,7 @@
     public static void verifyImeIsAlwaysHidden(View view) {
         always(
                 () ->
-                        assertWithMessage("IME should be hidden")
+                        assertWithMessage("IME should have been hidden")
                                 .that(callOnMainSync(() -> isImeShown(view)))
                                 .isFalse(),
                 TIMEOUT);
@@ -211,7 +221,8 @@
     public static void verifyWindowNeverGainsFocus(View view) {
         always(
                 () ->
-                        assertWithMessage("window should never gain focus")
+                        assertWithMessage(
+                                "Window should not have gained focus; value of hasWindowFocus:")
                                 .that(callOnMainSync(view::hasWindowFocus))
                                 .isFalse(),
                 TIMEOUT);
@@ -221,7 +232,7 @@
     public static void verifyViewNeverGainsFocus(View view) {
         always(
                 () ->
-                        assertWithMessage("view should never gain ime focus")
+                        assertWithMessage("View should not have gained focus; value of hasFocus:")
                                 .that(callOnMainSync(view::hasFocus))
                                 .isFalse(),
                 TIMEOUT);
@@ -254,8 +265,23 @@
         }
     }
 
+    /**
+     * Returns {@code true} if the activity can't receive IME focus, based on its window flags,
+     * and {@code false} otherwise.
+     *
+     * @param activity the activity to check.
+     */
     public static boolean hasUnfocusableWindowFlags(Activity activity) {
-        int windowFlags = activity.getWindow().getAttributes().flags;
+        return hasUnfocusableWindowFlags(activity.getWindow().getAttributes().flags);
+    }
+
+    /**
+     * Returns {@code true} if the activity can't receive IME focus, based on its window flags,
+     * and {@code false} otherwise.
+     *
+     * @param windowFlags the window flags to check.
+     */
+    public static boolean hasUnfocusableWindowFlags(int windowFlags) {
         return (windowFlags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0
                 || (windowFlags & LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
                 || (windowFlags & LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
@@ -302,22 +328,26 @@
 
         private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
                 new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+                    @NonNull
                     @Override
                     public WindowInsetsAnimation.Bounds onStart(
-                            WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) {
+                            @NonNull WindowInsetsAnimation animation,
+                            @NonNull WindowInsetsAnimation.Bounds bounds) {
                         mIsAnimating = true;
                         return super.onStart(animation, bounds);
                     }
 
                     @Override
-                    public void onEnd(WindowInsetsAnimation animation) {
+                    public void onEnd(@NonNull WindowInsetsAnimation animation) {
                         super.onEnd(animation);
                         mIsAnimating = false;
                     }
 
+                    @NonNull
                     @Override
                     public WindowInsets onProgress(
-                            WindowInsets insets, List<WindowInsetsAnimation> runningAnimations) {
+                            @NonNull WindowInsets insets,
+                            @NonNull List<WindowInsetsAnimation> runningAnimations) {
                         return insets;
                     }
                 };
@@ -394,7 +424,7 @@
                     getInputMethodManager()
                             .showSoftInput(mEditText, 0 /* flags */);
             if (showResult) {
-                Log.i(TAG, "IMM#showSoftInput successfully");
+                Log.i(TAG, "IMM#showSoftInput succeeded");
             } else {
                 Log.i(TAG, "IMM#showSoftInput failed");
             }
@@ -407,7 +437,7 @@
                     getInputMethodManager()
                             .hideSoftInputFromWindow(mEditText.getWindowToken(), 0 /* flags */);
             if (hideResult) {
-                Log.i(TAG, "IMM#hideSoftInput successfully");
+                Log.i(TAG, "IMM#hideSoftInput succeeded");
             } else {
                 Log.i(TAG, "IMM#hideSoftInput failed");
             }
@@ -421,7 +451,7 @@
             }
             Log.i(TAG, "showImeWithWIC()");
             WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
-            assertWithMessage("WindowInsetsController shouldn't be null.")
+            assertWithMessage("WindowInsetsController")
                     .that(windowInsetsController)
                     .isNotNull();
             windowInsetsController.show(WindowInsets.Type.ime());
@@ -434,7 +464,7 @@
             }
             Log.i(TAG, "hideImeWithWIC()");
             WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
-            assertWithMessage("WindowInsetsController shouldn't be null.")
+            assertWithMessage("WindowInsetsController")
                     .that(windowInsetsController)
                     .isNotNull();
             windowInsetsController.hide(WindowInsets.Type.ime());
@@ -482,13 +512,14 @@
             return mIsAnimating;
         }
 
-        public void requestFocus() {
-            boolean requestFocusResult = getEditText().requestFocus();
+        public boolean requestFocus() {
+            boolean requestFocusResult = mEditText.requestFocus();
             if (requestFocusResult) {
-                Log.i(TAG, "Request focus successfully");
+                Log.i(TAG, "View#requestFocus succeeded");
             } else {
-                Log.i(TAG, "Request focus failed");
+                Log.i(TAG, "View#requestFocus failed");
             }
+            return requestFocusResult;
         }
     }
 }