Merge "Use expression functions when appropriate" into main
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index b2ac640..4f1cd97 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -26,6 +26,7 @@
     <uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
     <uses-permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION" />
     <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" />
+    <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
 
     <application>
         <activity
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
index 8cd7b0f..82ef00e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.appzoomout;
 
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.app.ActivityManager;
@@ -100,6 +101,7 @@
 
         mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
         mDisplayController.addDisplayChangingController(this);
+        updateDisplayLayout(mContext.getDisplayId());
 
         mDisplayAreaOrganizer.registerOrganizer();
     }
@@ -135,7 +137,9 @@
     public void onDisplayChange(int displayId, int fromRotation, int toRotation,
             @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
         // TODO: verify if there is synchronization issues.
-        mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation);
+        if (toRotation != ROTATION_UNDEFINED) {
+            mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation);
+        }
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index f532be6..72be066 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -21,6 +21,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayTopology;
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Size;
@@ -34,6 +35,7 @@
 
 import androidx.annotation.BinderThread;
 
+import com.android.window.flags.Flags;
 import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ShellInit;
@@ -54,6 +56,7 @@
     private final ShellExecutor mMainExecutor;
     private final Context mContext;
     private final IWindowManager mWmService;
+    private final DisplayManager mDisplayManager;
     private final DisplayChangeController mChangeController;
     private final IDisplayWindowListener mDisplayContainerListener;
 
@@ -61,10 +64,11 @@
     private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
 
     public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
-            ShellExecutor mainExecutor) {
+            ShellExecutor mainExecutor, DisplayManager displayManager) {
         mMainExecutor = mainExecutor;
         mContext = context;
         mWmService = wmService;
+        mDisplayManager = displayManager;
         // TODO: Inject this instead
         mChangeController = new DisplayChangeController(mWmService, shellInit, mainExecutor);
         mDisplayContainerListener = new DisplayWindowListenerImpl();
@@ -74,7 +78,7 @@
     }
 
     /**
-     * Initializes the window listener.
+     * Initializes the window listener and the topology listener.
      */
     public void onInit() {
         try {
@@ -82,6 +86,12 @@
             for (int i = 0; i < displayIds.length; i++) {
                 onDisplayAdded(displayIds[i]);
             }
+
+            if (Flags.enableConnectedDisplaysWindowDrag()) {
+                mDisplayManager.registerTopologyListener(mMainExecutor,
+                        this::onDisplayTopologyChanged);
+                onDisplayTopologyChanged(mDisplayManager.getDisplayTopology());
+            }
         } catch (RemoteException e) {
             throw new RuntimeException("Unable to register display controller");
         }
@@ -91,8 +101,7 @@
      * Gets a display by id from DisplayManager.
      */
     public Display getDisplay(int displayId) {
-        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-        return displayManager.getDisplay(displayId);
+        return mDisplayManager.getDisplay(displayId);
     }
 
     /**
@@ -221,6 +230,14 @@
         }
     }
 
+    private void onDisplayTopologyChanged(DisplayTopology topology) {
+        // TODO(b/381472611): Call DisplayTopology#getCoordinates and update values in
+        //                    DisplayLayout when DM code is ready.
+        for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
+            mDisplayChangedListeners.get(i).onTopologyChanged();
+        }
+    }
+
     private void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
         synchronized (mDisplays) {
             final DisplayRecord dr = mDisplays.get(displayId);
@@ -408,5 +425,10 @@
          */
         default void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
                 Set<Rect> unrestricted) {}
+
+        /**
+         * Called when the display topology has changed.
+         */
+        default void onTopologyChanged() {}
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index b6a1686..4973a6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -31,7 +31,9 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Insets;
+import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
@@ -71,9 +73,12 @@
     public static final int NAV_BAR_RIGHT = 1 << 1;
     public static final int NAV_BAR_BOTTOM = 1 << 2;
 
+    private static final String TAG = "DisplayLayout";
+
     private int mUiMode;
     private int mWidth;
     private int mHeight;
+    private RectF mGlobalBoundsDp;
     private DisplayCutout mCutout;
     private int mRotation;
     private int mDensityDpi;
@@ -109,6 +114,7 @@
         return mUiMode == other.mUiMode
                 && mWidth == other.mWidth
                 && mHeight == other.mHeight
+                && Objects.equals(mGlobalBoundsDp, other.mGlobalBoundsDp)
                 && Objects.equals(mCutout, other.mCutout)
                 && mRotation == other.mRotation
                 && mDensityDpi == other.mDensityDpi
@@ -127,8 +133,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi,
-                mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
+        return Objects.hash(mUiMode, mWidth, mHeight, mGlobalBoundsDp, mCutout, mRotation,
+                mDensityDpi, mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
                 mNavBarFrameHeight, mTaskbarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
                 mNavigationBarCanMove, mReverseDefaultRotation, mInsetsState);
     }
@@ -170,6 +176,7 @@
         mUiMode = dl.mUiMode;
         mWidth = dl.mWidth;
         mHeight = dl.mHeight;
+        mGlobalBoundsDp = dl.mGlobalBoundsDp;
         mCutout = dl.mCutout;
         mRotation = dl.mRotation;
         mDensityDpi = dl.mDensityDpi;
@@ -193,6 +200,7 @@
         mRotation = info.rotation;
         mCutout = info.displayCutout;
         mDensityDpi = info.logicalDensityDpi;
+        mGlobalBoundsDp = new RectF(0, 0, pxToDp(mWidth), pxToDp(mHeight));
         mHasNavigationBar = hasNavigationBar;
         mHasStatusBar = hasStatusBar;
         mAllowSeamlessRotationDespiteNavBarMoving = res.getBoolean(
@@ -255,6 +263,11 @@
         recalcInsets(res);
     }
 
+    /** Update the global bounds of this layout, in DP. */
+    public void setGlobalBoundsDp(RectF bounds) {
+        mGlobalBoundsDp = bounds;
+    }
+
     /** Get this layout's non-decor insets. */
     public Rect nonDecorInsets() {
         return mNonDecorInsets;
@@ -265,16 +278,21 @@
         return mStableInsets;
     }
 
-    /** Get this layout's width. */
+    /** Get this layout's width in pixels. */
     public int width() {
         return mWidth;
     }
 
-    /** Get this layout's height. */
+    /** Get this layout's height in pixels. */
     public int height() {
         return mHeight;
     }
 
+    /** Get this layout's global bounds in the multi-display coordinate system in DP. */
+    public RectF globalBoundsDp() {
+        return mGlobalBoundsDp;
+    }
+
     /** Get this layout's display rotation. */
     public int rotation() {
         return mRotation;
@@ -486,4 +504,48 @@
                 ? R.dimen.navigation_bar_frame_height_landscape
                 : R.dimen.navigation_bar_frame_height);
     }
+
+    /**
+     * Converts a pixel value to a density-independent pixel (dp) value.
+     *
+     * @param px The pixel value to convert.
+     * @return The equivalent value in DP units.
+     */
+    public float pxToDp(Number px) {
+        return px.floatValue() * DisplayMetrics.DENSITY_DEFAULT / mDensityDpi;
+    }
+
+    /**
+     * Converts a density-independent pixel (dp) value to a pixel value.
+     *
+     * @param dp The DP value to convert.
+     * @return The equivalent value in pixel units.
+     */
+    public float dpToPx(Number dp) {
+        return dp.floatValue() * mDensityDpi / DisplayMetrics.DENSITY_DEFAULT;
+    }
+
+    /**
+     * Converts local pixel coordinates on this layout to global DP coordinates.
+     *
+     * @param xPx The x-coordinate in pixels, relative to the layout's origin.
+     * @param yPx The y-coordinate in pixels, relative to the layout's origin.
+     * @return A PointF object representing the coordinates in global DP units.
+     */
+    public PointF localPxToGlobalDp(Number xPx, Number yPx) {
+        return new PointF(mGlobalBoundsDp.left + pxToDp(xPx),
+                mGlobalBoundsDp.top + pxToDp(yPx));
+    }
+
+    /**
+     * Converts global DP coordinates to local pixel coordinates on this layout.
+     *
+     * @param xDp The x-coordinate in global DP units.
+     * @param yDp The y-coordinate in global DP units.
+     * @return A PointF object representing the coordinates in local pixel units on this layout.
+     */
+    public PointF globalDpToLocalPx(Number xDp, Number yDp) {
+        return new PointF(dpToPx(xDp.floatValue() - mGlobalBoundsDp.left),
+                dpToPx(yDp.floatValue() - mGlobalBoundsDp.top));
+    }
 }
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 ab3c33e..cbbe8a2 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
@@ -25,6 +25,7 @@
 import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.provider.Settings;
@@ -175,8 +176,9 @@
     static DisplayController provideDisplayController(Context context,
             IWindowManager wmService,
             ShellInit shellInit,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new DisplayController(context, wmService, shellInit, mainExecutor);
+            @ShellMainThread ShellExecutor mainExecutor,
+            DisplayManager displayManager) {
+        return new DisplayController(context, wmService, shellInit, mainExecutor, displayManager);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
index 1e5e153..d3de0f7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
+import android.hardware.display.DisplayManager;
 import android.view.IWindowManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -50,12 +51,14 @@
     private @Mock IWindowManager mWM;
     private @Mock ShellInit mShellInit;
     private @Mock ShellExecutor mMainExecutor;
+    private @Mock DisplayManager mDisplayManager;
     private DisplayController mController;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mController = new DisplayController(mContext, mWM, mShellInit, mMainExecutor);
+        mController = new DisplayController(
+                mContext, mWM, mShellInit, mMainExecutor, mDisplayManager);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
index d467b39..b0a455d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
@@ -33,7 +33,9 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Insets;
+import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 
@@ -58,6 +60,7 @@
 @SmallTest
 public class DisplayLayoutTest extends ShellTestCase {
     private MockitoSession mMockitoSession;
+    private static final float DELTA = 0.1f; // Constant for assertion delta
 
     @Before
     public void setup() {
@@ -130,6 +133,39 @@
         assertEquals(new Rect(40, 0, 60, 0), dl.nonDecorInsets());
     }
 
+    @Test
+    public void testDpPxConversion() {
+        int px = 100;
+        float dp = 53.33f;
+        int xPx = 100;
+        int yPx = 200;
+        float xDp = 164.33f;
+        float yDp = 328.66f;
+
+        Resources res = createResources(40, 50, false);
+        DisplayInfo info = createDisplayInfo(1000, 1500, 0, ROTATION_0);
+        DisplayLayout dl = new DisplayLayout(info, res, false, false);
+        dl.setGlobalBoundsDp(new RectF(111f, 222f, 300f, 400f));
+
+        // Test pxToDp
+        float resultDp = dl.pxToDp(px);
+        assertEquals(dp, resultDp, DELTA);
+
+        // Test dpToPx
+        float resultPx = dl.dpToPx(dp);
+        assertEquals(px, resultPx, DELTA);
+
+        // Test localPxToGlobalDp
+        PointF resultGlobalDp = dl.localPxToGlobalDp(xPx, yPx);
+        assertEquals(xDp, resultGlobalDp.x, DELTA);
+        assertEquals(yDp, resultGlobalDp.y, DELTA);
+
+        // Test globalDpToLocalPx
+        PointF resultLocalPx = dl.globalDpToLocalPx(xDp, yDp);
+        assertEquals(xPx, resultLocalPx.x, DELTA);
+        assertEquals(yPx, resultLocalPx.y, DELTA);
+    }
+
     private Resources createResources(int navLand, int navPort, boolean navMoves) {
         Configuration cfg = new Configuration();
         cfg.uiMode = UI_MODE_TYPE_NORMAL;
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/DataChangeReason.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/DataChangeReason.kt
index 145fabe..ac36b08 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/DataChangeReason.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/DataChangeReason.kt
@@ -39,5 +39,7 @@
         const val RESTORE = 3
         /** Data is synced from another profile (e.g. personal profile to work profile). */
         const val SYNC_ACROSS_PROFILES = 4
+
+        fun isDataChange(reason: Int): Boolean = reason in UNKNOWN..SYNC_ACROSS_PROFILES
     }
 }
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
index e5bf41f..83725aa 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -44,6 +44,24 @@
     }
 }
 
+/** The reason of preference change. */
+@IntDef(
+    PreferenceChangeReason.VALUE,
+    PreferenceChangeReason.STATE,
+    PreferenceChangeReason.DEPENDENT,
+)
+@Retention(AnnotationRetention.SOURCE)
+annotation class PreferenceChangeReason {
+    companion object {
+        /** Preference value is changed. */
+        const val VALUE = 1000
+        /** Preference state (title/summary, enable state, etc.) is changed. */
+        const val STATE = 1001
+        /** Dependent preference state is changed. */
+        const val DEPENDENT = 1002
+    }
+}
+
 /** Indicates how sensitive of the data. */
 @Retention(AnnotationRetention.SOURCE)
 @Target(AnnotationTarget.TYPE)
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 91abd8b..8358ab9 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -25,12 +25,14 @@
 import androidx.preference.PreferenceDataStore
 import androidx.preference.PreferenceGroup
 import androidx.preference.PreferenceScreen
+import com.android.settingslib.datastore.DataChangeReason
 import com.android.settingslib.datastore.HandlerExecutor
 import com.android.settingslib.datastore.KeyValueStore
 import com.android.settingslib.datastore.KeyedDataObservable
 import com.android.settingslib.datastore.KeyedObservable
 import com.android.settingslib.datastore.KeyedObserver
 import com.android.settingslib.metadata.PersistentPreference
+import com.android.settingslib.metadata.PreferenceChangeReason
 import com.android.settingslib.metadata.PreferenceHierarchy
 import com.android.settingslib.metadata.PreferenceHierarchyNode
 import com.android.settingslib.metadata.PreferenceLifecycleContext
@@ -73,7 +75,7 @@
                     ?.keyValueStore
 
             override fun notifyPreferenceChange(key: String) =
-                notifyChange(key, CHANGE_REASON_STATE)
+                notifyChange(key, PreferenceChangeReason.STATE)
 
             @Suppress("DEPRECATION")
             override fun startActivityForResult(
@@ -91,7 +93,13 @@
     private val preferenceObserver: KeyedObserver<String?>
 
     private val storageObserver =
-        KeyedObserver<String> { key, _ -> notifyChange(key, CHANGE_REASON_VALUE) }
+        KeyedObserver<String> { key, reason ->
+            if (DataChangeReason.isDataChange(reason)) {
+                notifyChange(key, PreferenceChangeReason.VALUE)
+            } else {
+                notifyChange(key, PreferenceChangeReason.STATE)
+            }
+        }
 
     init {
         val preferencesBuilder = ImmutableMap.builder<String, PreferenceHierarchyNode>()
@@ -148,7 +156,7 @@
         }
 
         // check reason to avoid potential infinite loop
-        if (reason != CHANGE_REASON_DEPENDENT) {
+        if (reason != PreferenceChangeReason.DEPENDENT) {
             notifyDependents(key, mutableSetOf())
         }
     }
@@ -157,7 +165,7 @@
     private fun notifyDependents(key: String, notifiedKeys: MutableSet<String>) {
         if (!notifiedKeys.add(key)) return
         for (dependency in dependencies[key]) {
-            notifyChange(dependency, CHANGE_REASON_DEPENDENT)
+            notifyChange(dependency, PreferenceChangeReason.DEPENDENT)
             notifyDependents(dependency, notifiedKeys)
         }
     }
@@ -210,13 +218,6 @@
     }
 
     companion object {
-        /** Preference value is changed. */
-        const val CHANGE_REASON_VALUE = 0
-        /** Preference state (title/summary, enable state, etc.) is changed. */
-        const val CHANGE_REASON_STATE = 1
-        /** Dependent preference state is changed. */
-        const val CHANGE_REASON_DEPENDENT = 2
-
         /** Updates preference screen that has incomplete hierarchy. */
         @JvmStatic
         fun bind(preferenceScreen: PreferenceScreen) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 6be4336..155c7e6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -21,6 +21,7 @@
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
 
+import android.annotation.CallbackExecutor;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -39,6 +40,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 public class LeAudioProfile implements LocalBluetoothProfile {
     private static final String TAG = "LeAudioProfile";
@@ -317,6 +319,78 @@
         return mService.getAudioLocation(device);
     }
 
+    /**
+     * Sets the fallback group id when broadcast switches to unicast.
+     *
+     * @param groupId the target fallback group id
+     */
+    public void setBroadcastToUnicastFallbackGroup(int groupId) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot set fallback group: " + groupId);
+            return;
+        }
+
+        mService.setBroadcastToUnicastFallbackGroup(groupId);
+    }
+
+    /**
+     * Gets the fallback group id when broadcast switches to unicast.
+     *
+     * @return current fallback group id
+     */
+    public int getBroadcastToUnicastFallbackGroup() {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot get fallback group.");
+            return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+        }
+        return mService.getBroadcastToUnicastFallbackGroup();
+    }
+
+    /**
+     * Registers a {@link BluetoothLeAudio.Callback} that will be invoked during the
+     * operation of this profile.
+     *
+     * Repeated registration of the same <var>callback</var> object after the first call to this
+     * method will result with IllegalArgumentException being thrown, even when the
+     * <var>executor</var> is different. API caller would have to call
+     * {@link #unregisterCallback(BluetoothLeAudio.Callback)} with the same callback object
+     * before registering it again.
+     *
+     * @param executor an {@link Executor} to execute given callback
+     * @param callback user implementation of the {@link BluetoothLeAudio.Callback}
+     * @throws NullPointerException if a null executor, or callback is given, or
+     *                              IllegalArgumentException if the same <var>callback</var> is
+     *                              already registered.
+     */
+    public void registerCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull BluetoothLeAudio.Callback callback) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot register callback.");
+            return;
+        }
+        mService.registerCallback(executor, callback);
+    }
+
+    /**
+     * Unregisters the specified {@link BluetoothLeAudio.Callback}.
+     * <p>The same {@link BluetoothLeAudio.Callback} object used when calling
+     * {@link #registerCallback(Executor, BluetoothLeAudio.Callback)} must be used.
+     *
+     * <p>Callbacks are automatically unregistered when application process goes away
+     *
+     * @param callback user implementation of the {@link BluetoothLeAudio.Callback}
+     * @throws NullPointerException when callback is null or IllegalArgumentException when no
+     *                              callback is registered
+     */
+    public void unregisterCallback(@NonNull BluetoothLeAudio.Callback callback) {
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service. Cannot unregister callback.");
+            return;
+        }
+        mService.unregisterCallback(callback);
+    }
+
     @RequiresApi(Build.VERSION_CODES.S)
     protected void finalize() {
         if (DEBUG) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
index 96401ce..a27bf8a 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -444,7 +444,7 @@
         val left = available - consumed
         val postConsumed =
             nestedScrollDispatcher.dispatchPostScroll(
-                consumed = preConsumed + consumed,
+                consumed = consumed,
                 available = left,
                 source = NestedScrollSource.UserInput,
             )
@@ -482,10 +482,9 @@
         val available = velocity - preConsumed
         val consumed = performFling(available)
         val left = available - consumed
-        return nestedScrollDispatcher.dispatchPostFling(
-            consumed = consumed + preConsumed,
-            available = left,
-        )
+        val postConsumed =
+            nestedScrollDispatcher.dispatchPostFling(consumed = consumed, available = left)
+        return preConsumed + consumed + postConsumed
     }
 
     /*
@@ -549,9 +548,10 @@
             nestedScrollController == null &&
                 // TODO(b/388231324): Remove this.
                 !lastEventWasScrollWheel &&
-                draggable.shouldConsumeNestedScroll(sign)
+                draggable.shouldConsumeNestedScroll(sign) &&
+                lastFirstDown != null
         ) {
-            val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" }
+            val startedPosition = checkNotNull(lastFirstDown)
 
             // TODO(b/382665591): Ensure that there is at least one pointer down.
             val pointersDownCount = pointersDown.size.coerceAtLeast(1)
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
index 5de0f12..19d28cc 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -36,6 +36,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerType
@@ -52,6 +53,7 @@
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeDown
 import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeWithVelocity
 import androidx.compose.ui.unit.Velocity
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.ceil
@@ -773,6 +775,181 @@
         rule.onNodeWithTag(buttonTag).assertTextEquals("Count: 3")
     }
 
+    @Test
+    fun nestedDragNotStartedWhenEnabledAfterDragStarted() {
+        val draggable = TestDraggable()
+        var enabled by mutableStateOf(false)
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(
+                    Modifier.fillMaxSize()
+                        .nestedDraggable(draggable, orientation, enabled = enabled)
+                        .scrollable(rememberScrollableState { 0f }, orientation)
+                )
+            }
+
+        rule.onRoot().performTouchInput { down(center) }
+
+        enabled = true
+        rule.waitForIdle()
+
+        rule.onRoot().performTouchInput { moveBy((touchSlop + 1f).toOffset()) }
+
+        assertThat(draggable.onDragStartedCalled).isFalse()
+    }
+
+    @Test
+    fun availableAndConsumedScrollDeltas() {
+        val totalScroll = 200f
+        val consumedByEffectPreScroll = 10f // 200f => 190f
+        val consumedByConnectionPreScroll = 20f // 190f => 170f
+        val consumedByScroll = 30f // 170f => 140f
+        val consumedByConnectionPostScroll = 40f // 140f => 100f
+
+        // Available scroll values that we will check later.
+        var availableToEffectPreScroll = 0f
+        var availableToConnectionPreScroll = 0f
+        var availableToScroll = 0f
+        var availableToConnectionPostScroll = 0f
+        var availableToEffectPostScroll = 0f
+
+        val effect =
+            TestOverscrollEffect(
+                orientation,
+                onPreScroll = {
+                    availableToEffectPreScroll = it
+                    consumedByEffectPreScroll
+                },
+                onPostScroll = {
+                    availableToEffectPostScroll = it
+                    it
+                },
+            )
+
+        val connection =
+            object : NestedScrollConnection {
+                override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                    availableToConnectionPreScroll = available.toFloat()
+                    return consumedByConnectionPreScroll.toOffset()
+                }
+
+                override fun onPostScroll(
+                    consumed: Offset,
+                    available: Offset,
+                    source: NestedScrollSource,
+                ): Offset {
+                    assertThat(consumed.toFloat()).isEqualTo(consumedByScroll)
+                    availableToConnectionPostScroll = available.toFloat()
+                    return consumedByConnectionPostScroll.toOffset()
+                }
+            }
+
+        val draggable =
+            TestDraggable(
+                onDrag = {
+                    availableToScroll = it
+                    consumedByScroll
+                }
+            )
+
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(
+                    Modifier.fillMaxSize()
+                        .nestedScroll(connection)
+                        .nestedDraggable(draggable, orientation, effect)
+                )
+            }
+
+        rule.onRoot().performTouchInput {
+            down(center)
+            moveBy((touchSlop + totalScroll).toOffset())
+        }
+
+        assertThat(availableToEffectPreScroll).isEqualTo(200f)
+        assertThat(availableToConnectionPreScroll).isEqualTo(190f)
+        assertThat(availableToScroll).isEqualTo(170f)
+        assertThat(availableToConnectionPostScroll).isEqualTo(140f)
+        assertThat(availableToEffectPostScroll).isEqualTo(100f)
+    }
+
+    @Test
+    fun availableAndConsumedVelocities() {
+        val totalVelocity = 200f
+        val consumedByEffectPreFling = 10f // 200f => 190f
+        val consumedByConnectionPreFling = 20f // 190f => 170f
+        val consumedByFling = 30f // 170f => 140f
+        val consumedByConnectionPostFling = 40f // 140f => 100f
+
+        // Available velocities that we will check later.
+        var availableToEffectPreFling = 0f
+        var availableToConnectionPreFling = 0f
+        var availableToFling = 0f
+        var availableToConnectionPostFling = 0f
+        var availableToEffectPostFling = 0f
+
+        val effect =
+            TestOverscrollEffect(
+                orientation,
+                onPreFling = {
+                    availableToEffectPreFling = it
+                    consumedByEffectPreFling
+                },
+                onPostFling = {
+                    availableToEffectPostFling = it
+                    it
+                },
+                onPostScroll = { 0f },
+            )
+
+        val connection =
+            object : NestedScrollConnection {
+                override suspend fun onPreFling(available: Velocity): Velocity {
+                    availableToConnectionPreFling = available.toFloat()
+                    return consumedByConnectionPreFling.toVelocity()
+                }
+
+                override suspend fun onPostFling(
+                    consumed: Velocity,
+                    available: Velocity,
+                ): Velocity {
+                    assertThat(consumed.toFloat()).isEqualTo(consumedByFling)
+                    availableToConnectionPostFling = available.toFloat()
+                    return consumedByConnectionPostFling.toVelocity()
+                }
+            }
+
+        val draggable =
+            TestDraggable(
+                onDragStopped = { velocity, _ ->
+                    availableToFling = velocity
+                    consumedByFling
+                },
+                onDrag = { 0f },
+            )
+
+        rule.setContent {
+            Box(
+                Modifier.fillMaxSize()
+                    .nestedScroll(connection)
+                    .nestedDraggable(draggable, orientation, effect)
+            )
+        }
+
+        rule.onRoot().performTouchInput {
+            when (orientation) {
+                Orientation.Horizontal -> swipeWithVelocity(topLeft, topRight, totalVelocity)
+                Orientation.Vertical -> swipeWithVelocity(topLeft, bottomLeft, totalVelocity)
+            }
+        }
+
+        assertThat(availableToEffectPreFling).isWithin(1f).of(200f)
+        assertThat(availableToConnectionPreFling).isWithin(1f).of(190f)
+        assertThat(availableToFling).isWithin(1f).of(170f)
+        assertThat(availableToConnectionPostFling).isWithin(1f).of(140f)
+        assertThat(availableToEffectPostFling).isWithin(1f).of(100f)
+    }
+
     private fun ComposeContentTestRule.setContentWithTouchSlop(
         content: @Composable () -> Unit
     ): Float {
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/TestOverscrollEffect.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/TestOverscrollEffect.kt
index 8bf9c21..0659f919 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/TestOverscrollEffect.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/TestOverscrollEffect.kt
@@ -24,6 +24,8 @@
 
 class TestOverscrollEffect(
     override val orientation: Orientation,
+    private val onPreScroll: (Float) -> Float = { 0f },
+    private val onPreFling: suspend (Float) -> Float = { 0f },
     private val onPostFling: suspend (Float) -> Float = { it },
     private val onPostScroll: (Float) -> Float,
 ) : OverscrollEffect, OrientationAware {
@@ -36,19 +38,23 @@
         source: NestedScrollSource,
         performScroll: (Offset) -> Offset,
     ): Offset {
-        val consumedByScroll = performScroll(delta)
-        val available = delta - consumedByScroll
-        val consumedByEffect = onPostScroll(available.toFloat()).toOffset()
-        return consumedByScroll + consumedByEffect
+        val consumedByPreScroll = onPreScroll(delta.toFloat()).toOffset()
+        val availableToScroll = delta - consumedByPreScroll
+        val consumedByScroll = performScroll(availableToScroll)
+        val availableToPostScroll = availableToScroll - consumedByScroll
+        val consumedByPostScroll = onPostScroll(availableToPostScroll.toFloat()).toOffset()
+        return consumedByPreScroll + consumedByScroll + consumedByPostScroll
     }
 
     override suspend fun applyToFling(
         velocity: Velocity,
         performFling: suspend (Velocity) -> Velocity,
     ) {
-        val consumedByFling = performFling(velocity)
-        val available = velocity - consumedByFling
-        onPostFling(available.toFloat())
+        val consumedByPreFling = onPreFling(velocity.toFloat()).toVelocity()
+        val availableToFling = velocity - consumedByPreFling
+        val consumedByFling = performFling(availableToFling)
+        val availableToPostFling = availableToFling - consumedByFling
+        onPostFling(availableToPostFling.toFloat())
         applyToFlingDone = true
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index 2665910..6edf949 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -65,7 +65,7 @@
     @Before
     public void setUp() throws Exception {
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
-        final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings();
+        final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings(mContext);
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 mockSecureSettings, mHearingAidDeviceManager);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 241da5f..15afd25 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -71,7 +71,7 @@
     private AccessibilityManager mAccessibilityManager;
     @Mock
     private HearingAidDeviceManager mHearingAidDeviceManager;
-    private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
+    private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings(mContext);
     private RecyclerView mStubListView;
     private MenuView mMenuView;
     private MenuViewLayer mMenuViewLayer;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 715c40a..56a97bb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -89,7 +89,7 @@
     @Before
     public void setUp() throws Exception {
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        final SecureSettings secureSettings = TestUtils.mockSecureSettings();
+        final SecureSettings secureSettings = TestUtils.mockSecureSettings(mContext);
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 secureSettings, mHearingAidDeviceManager);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index cb7c205..5ff7bd0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -91,7 +91,7 @@
         mSpyContext = spy(mContext);
         doNothing().when(mSpyContext).startActivity(any());
 
-        final SecureSettings secureSettings = TestUtils.mockSecureSettings();
+        final SecureSettings secureSettings = TestUtils.mockSecureSettings(mContext);
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 secureSettings, mHearingAidDeviceManager);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/utils/TestUtils.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/utils/TestUtils.java
index 8399fa8..aafb212 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/utils/TestUtils.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/utils/TestUtils.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -76,8 +77,10 @@
      * Returns a mock secure settings configured to return information needed for tests.
      * Currently, this only includes button targets.
      */
-    public static SecureSettings mockSecureSettings() {
+    public static SecureSettings mockSecureSettings(Context context) {
         SecureSettings secureSettings = mock(SecureSettings.class);
+        when(secureSettings.getRealUserHandle(UserHandle.USER_CURRENT))
+                .thenReturn(context.getUserId());
 
         final String targets = getShortcutTargets(
                 Set.of(TEST_COMPONENT_A, TEST_COMPONENT_B));
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 58f2d3c..67f620f 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -19,6 +19,7 @@
     android:id="@+id/volume_dialog_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:alpha="0"
     android:clipChildren="false"
     app:layoutDescription="@xml/volume_dialog_scene">
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index 04afd86..caf043a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -22,6 +22,8 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.UiContext;
 import android.content.ComponentCallbacks;
 import android.content.Context;
@@ -44,7 +46,8 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
@@ -57,12 +60,16 @@
 import com.android.systemui.res.R;
 import com.android.systemui.util.leak.RotationUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 public class FullscreenMagnificationController implements ComponentCallbacks {
 
-    private static final String TAG = "FullscreenMagnificationController";
+    private static final String TAG = "FullscreenMagController";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
     private final Context mContext;
     private final AccessibilityManager mAccessibilityManager;
     private final WindowManager mWindowManager;
@@ -77,12 +84,14 @@
     private int mBorderStoke;
     private final int mDisplayId;
     private static final Region sEmptyRegion = new Region();
-    private ValueAnimator mShowHideBorderAnimator;
+    @VisibleForTesting
+    @Nullable
+    ValueAnimator mShowHideBorderAnimator;
     private Handler mHandler;
     private Executor mExecutor;
-    private boolean mFullscreenMagnificationActivated = false;
     private final Configuration mConfiguration;
-    private final Runnable mShowBorderRunnable = this::showBorderWithNullCheck;
+    private final Runnable mHideBorderImmediatelyRunnable = this::hideBorderImmediately;
+    private final Runnable mShowBorderRunnable = this::showBorder;
     private int mRotation;
     private final IRotationWatcher mRotationWatcher = new IRotationWatcher.Stub() {
         @Override
@@ -95,6 +104,21 @@
     private final DisplayManager.DisplayListener mDisplayListener;
     private String mCurrentDisplayUniqueId;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            DISABLED,
+            DISABLING,
+            ENABLING,
+            ENABLED
+    })
+    @interface FullscreenMagnificationActivationState {}
+    private static final int DISABLED = 0;
+    private static final int DISABLING  = 1;
+    private static final int ENABLING = 2;
+    private static final int ENABLED = 3;
+    @FullscreenMagnificationActivationState
+    private int mActivationState = DISABLED;
+
     public FullscreenMagnificationController(
             @UiContext Context context,
             @Main Handler handler,
@@ -106,7 +130,7 @@
             Supplier<SurfaceControlViewHost> scvhSupplier) {
         this(context, handler, executor, displayManager, accessibilityManager,
                 windowManager, iWindowManager, scvhSupplier,
-                new SurfaceControl.Transaction(), null);
+                new SurfaceControl.Transaction());
     }
 
     @VisibleForTesting
@@ -119,8 +143,7 @@
             WindowManager windowManager,
             IWindowManager iWindowManager,
             Supplier<SurfaceControlViewHost> scvhSupplier,
-            SurfaceControl.Transaction transaction,
-            ValueAnimator valueAnimator) {
+            SurfaceControl.Transaction transaction) {
         mContext = context;
         mHandler = handler;
         mExecutor = executor;
@@ -135,18 +158,6 @@
         mConfiguration = new Configuration(context.getResources().getConfiguration());
         mLongAnimationTimeMs = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_longAnimTime);
-        mShowHideBorderAnimator = (valueAnimator == null)
-                ? createNullTargetObjectAnimator() : valueAnimator;
-        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
-                if (isReverse) {
-                    // The animation was played in reverse, which means we are hiding the border.
-                    // We would like to perform clean up after the border is fully hidden.
-                    cleanUpBorder();
-                }
-            }
-        });
         mCurrentDisplayUniqueId = mContext.getDisplayNoVerify().getUniqueId();
         mDisplayManager = displayManager;
         mDisplayListener = new DisplayManager.DisplayListener() {
@@ -167,20 +178,51 @@
                     // Same unique ID means the physical display doesn't change. Early return.
                     return;
                 }
-
                 mCurrentDisplayUniqueId = uniqueId;
-                applyCornerRadiusToBorder();
+                mHandler.post(FullscreenMagnificationController.this::applyCornerRadiusToBorder);
             }
         };
     }
 
-    private ValueAnimator createNullTargetObjectAnimator() {
+    @VisibleForTesting
+    @UiThread
+    ValueAnimator createShowTargetAnimator(@NonNull View target) {
+        if (mShowHideBorderAnimator != null) {
+            mShowHideBorderAnimator.cancel();
+        }
+
         final ValueAnimator valueAnimator =
-                ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
-        Interpolator interpolator = new AccelerateDecelerateInterpolator();
+                ObjectAnimator.ofFloat(target, View.ALPHA, 0f, 1f);
+        Interpolator interpolator = new AccelerateInterpolator();
 
         valueAnimator.setInterpolator(interpolator);
         valueAnimator.setDuration(mLongAnimationTimeMs);
+        valueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation) {
+                mHandler.post(() -> setState(ENABLED));
+            }});
+        return valueAnimator;
+    }
+
+    @VisibleForTesting
+    @UiThread
+    ValueAnimator createHideTargetAnimator(@NonNull View target) {
+        if (mShowHideBorderAnimator != null) {
+            mShowHideBorderAnimator.cancel();
+        }
+
+        final ValueAnimator valueAnimator =
+                ObjectAnimator.ofFloat(target, View.ALPHA, 1f, 0f);
+        Interpolator interpolator = new DecelerateInterpolator();
+
+        valueAnimator.setInterpolator(interpolator);
+        valueAnimator.setDuration(mLongAnimationTimeMs);
+        valueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation) {
+                mHandler.post(() -> cleanUpBorder());
+            }});
         return valueAnimator;
     }
 
@@ -190,14 +232,10 @@
      */
     @UiThread
     public void onFullscreenMagnificationActivationChanged(boolean activated) {
-        final boolean changed = (mFullscreenMagnificationActivated != activated);
-        if (changed) {
-            mFullscreenMagnificationActivated = activated;
-            if (activated) {
-                createFullscreenMagnificationBorder();
-            } else {
-                removeFullscreenMagnificationBorder();
-            }
+        if (activated) {
+            createFullscreenMagnificationBorder();
+        } else {
+            removeFullscreenMagnificationBorder();
         }
     }
 
@@ -207,16 +245,21 @@
      */
     @UiThread
     private void removeFullscreenMagnificationBorder() {
-        if (mHandler.hasCallbacks(mShowBorderRunnable)) {
-            mHandler.removeCallbacks(mShowBorderRunnable);
+        int state = getState();
+        if (state == DISABLING || state == DISABLED) {
+            // If there is an ongoing disable process or it is already disabled, return
+            return;
         }
-        mContext.unregisterComponentCallbacks(this);
-
-
-        mShowHideBorderAnimator.reverse();
+        setState(DISABLING);
+        mShowHideBorderAnimator = createHideTargetAnimator(mFullscreenBorder);
+        mShowHideBorderAnimator.start();
     }
 
-    private void cleanUpBorder() {
+    @VisibleForTesting
+    @UiThread
+    void cleanUpBorder() {
+        mContext.unregisterComponentCallbacks(this);
+
         if (Flags.updateCornerRadiusOnDisplayChanged()) {
             mDisplayManager.unregisterDisplayListener(mDisplayListener);
         }
@@ -227,6 +270,12 @@
         }
 
         if (mFullscreenBorder != null) {
+            if (mHandler.hasCallbacks(mHideBorderImmediatelyRunnable)) {
+                mHandler.removeCallbacks(mHideBorderImmediatelyRunnable);
+            }
+            if (mHandler.hasCallbacks(mShowBorderRunnable)) {
+                mHandler.removeCallbacks(mShowBorderRunnable);
+            }
             mFullscreenBorder = null;
             try {
                 mIWindowManager.removeRotationWatcher(mRotationWatcher);
@@ -234,6 +283,7 @@
                 Log.w(TAG, "Failed to remove rotation watcher", e);
             }
         }
+        setState(DISABLED);
     }
 
     /**
@@ -242,44 +292,47 @@
      */
     @UiThread
     private void createFullscreenMagnificationBorder() {
+        int state = getState();
+        if (state == ENABLING || state == ENABLED) {
+            // If there is an ongoing enable process or it is already enabled, return
+            return;
+        }
+        if (mShowHideBorderAnimator != null) {
+            mShowHideBorderAnimator.cancel();
+        }
+        setState(ENABLING);
+
         onConfigurationChanged(mContext.getResources().getConfiguration());
         mContext.registerComponentCallbacks(this);
 
         if (mSurfaceControlViewHost == null) {
-            // Create the view only if it does not exist yet. If we are trying to enable fullscreen
-            // magnification before it was fully disabled, we use the previous view instead of
-            // creating a new one.
+            // Create the view only if it does not exist yet. If we are trying to enable
+            // fullscreen magnification before it was fully disabled, we use the previous view
+            // instead of creating a new one.
             mFullscreenBorder = LayoutInflater.from(mContext)
                     .inflate(R.layout.fullscreen_magnification_border, null);
-            // Set the initial border view alpha manually so we won't show the border accidentally
-            // after we apply show() to the SurfaceControl and before the animation starts to run.
+            // Set the initial border view alpha manually so we won't show the border
+            // accidentally after we apply show() to the SurfaceControl and before the
+            // animation starts to run.
             mFullscreenBorder.setAlpha(0f);
-            mShowHideBorderAnimator.setTarget(mFullscreenBorder);
             mSurfaceControlViewHost = mScvhSupplier.get();
             mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
-            mBorderSurfaceControl = mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl();
+            mBorderSurfaceControl =
+                    mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl();
             try {
                 mIWindowManager.watchRotation(mRotationWatcher, Display.DEFAULT_DISPLAY);
             } catch (Exception e) {
                 Log.w(TAG, "Failed to register rotation watcher", e);
             }
             if (Flags.updateCornerRadiusOnDisplayChanged()) {
-                mHandler.post(this::applyCornerRadiusToBorder);
+                applyCornerRadiusToBorder();
             }
         }
 
         mTransaction
                 .addTransactionCommittedListener(
                         mExecutor,
-                        () -> {
-                            if (mShowHideBorderAnimator.isRunning()) {
-                                // Since the method is only called when there is an activation
-                                // status change, the running animator is hiding the border.
-                                mShowHideBorderAnimator.reverse();
-                            } else {
-                                mShowHideBorderAnimator.start();
-                            }
-                        })
+                        this::showBorder)
                 .setPosition(mBorderSurfaceControl, -mBorderOffset, -mBorderOffset)
                 .setLayer(mBorderSurfaceControl, Integer.MAX_VALUE)
                 .show(mBorderSurfaceControl)
@@ -380,19 +433,25 @@
             mHandler.removeCallbacks(mShowBorderRunnable);
         }
 
-        // We hide the border immediately as early as possible to beat the redrawing of window
-        // in response to the orientation change so users won't see a weird shape border.
-        mHandler.postAtFrontOfQueue(() -> {
-            mFullscreenBorder.setAlpha(0f);
-        });
-
+        // We hide the border immediately as early as possible to beat the redrawing of
+        // window in response to the orientation change so users won't see a weird shape
+        // border.
+        mHandler.postAtFrontOfQueue(mHideBorderImmediatelyRunnable);
         mHandler.postDelayed(mShowBorderRunnable, mLongAnimationTimeMs);
     }
 
-    private void showBorderWithNullCheck() {
+    @UiThread
+    private void hideBorderImmediately() {
         if (mShowHideBorderAnimator != null) {
-            mShowHideBorderAnimator.start();
+            mShowHideBorderAnimator.cancel();
         }
+        mFullscreenBorder.setAlpha(0f);
+    }
+
+    @UiThread
+    private void showBorder() {
+        mShowHideBorderAnimator = createShowTargetAnimator(mFullscreenBorder);
+        mShowHideBorderAnimator.start();
     }
 
     private void updateDimensions() {
@@ -404,7 +463,9 @@
                 R.dimen.magnifier_border_width_fullscreen_with_offset);
     }
 
-    private void applyCornerRadiusToBorder() {
+    @UiThread
+    @VisibleForTesting
+    void applyCornerRadiusToBorder() {
         if (!isActivated()) {
             return;
         }
@@ -422,6 +483,20 @@
         backgroundDrawable.setCornerRadius(cornerRadius);
     }
 
+    @UiThread
+    private void setState(@FullscreenMagnificationActivationState int state) {
+        if (DEBUG) {
+            Log.d(TAG, "setState from " + mActivationState + " to " + state);
+        }
+        mActivationState = state;
+    }
+
+    @VisibleForTesting
+    @UiThread
+    int getState() {
+        return mActivationState;
+    }
+
     @Override
     public void onLowMemory() {
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 5f0acfa..67aa4ff 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
-import android.os.UserHandle;
 import android.text.TextUtils;
 import android.view.Display;
 import android.view.WindowManager;
@@ -58,7 +57,7 @@
     private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
-    private Context mContext;
+    private final Context mContext;
     private final WindowManager mWindowManager;
     private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
     private final DisplayManager mDisplayManager;
@@ -226,7 +225,6 @@
         @Override
         public void onUserInitializationComplete(int userId) {
             mIsUserInInitialization = false;
-            mContext = mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
             mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
             mBtnTargets =
                     mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index 121b51f..a1cb036 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -79,6 +79,8 @@
     private static final int DEFAULT_MIGRATION_TOOLTIP_VALUE_PROMPT = MigrationPrompt.DISABLED;
 
     private final Context mContext;
+    // Pref always get the userId from the context to store SharedPreferences for the correct user
+    private final Context mCurrentUserContext;
     private final Configuration mConfiguration;
     private final AccessibilityManager mAccessibilityManager;
     private final AccessibilityManager.AccessibilityServicesStateChangeListener
@@ -157,6 +159,9 @@
             OnContentsChanged settingsContentsChanged, SecureSettings secureSettings,
             @Nullable HearingAidDeviceManager hearingAidDeviceManager) {
         mContext = context;
+        final int currentUserId = secureSettings.getRealUserHandle(UserHandle.USER_CURRENT);
+        mCurrentUserContext = context.createContextAsUser(
+                UserHandle.of(currentUserId), /* flags= */ 0);
         mAccessibilityManager = accessibilityManager;
         mConfiguration = new Configuration(context.getResources().getConfiguration());
         mSettingsContentsCallback = settingsContentsChanged;
@@ -168,12 +173,13 @@
 
     void loadMenuMoveToTucked(OnInfoReady<Boolean> callback) {
         callback.onReady(
-                Prefs.getBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
+                Prefs.getBoolean(
+                        mCurrentUserContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
                         DEFAULT_MOVE_TO_TUCKED_VALUE));
     }
 
     void loadDockTooltipVisibility(OnInfoReady<Boolean> callback) {
-        callback.onReady(Prefs.getBoolean(mContext,
+        callback.onReady(Prefs.getBoolean(mCurrentUserContext,
                 Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP,
                 DEFAULT_HAS_SEEN_DOCK_TOOLTIP_VALUE));
     }
@@ -215,19 +221,19 @@
     }
 
     void updateMoveToTucked(boolean isMoveToTucked) {
-        Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
+        Prefs.putBoolean(mCurrentUserContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
                 isMoveToTucked);
     }
 
     void updateMenuSavingPosition(Position percentagePosition) {
         mPercentagePosition = percentagePosition;
-        Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
+        Prefs.putString(mCurrentUserContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
                 percentagePosition.toString());
     }
 
     void updateDockTooltipVisibility(boolean hasSeen) {
-        Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP,
-                hasSeen);
+        Prefs.putBoolean(mCurrentUserContext,
+                Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, hasSeen);
     }
 
     void updateMigrationTooltipVisibility(boolean visible) {
@@ -243,7 +249,7 @@
     }
 
     private Position getStartPosition() {
-        final String absolutePositionString = Prefs.getString(mContext,
+        final String absolutePositionString = Prefs.getString(mCurrentUserContext,
                 Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
 
         final float defaultPositionXPercent =
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index 184518a..e7470a3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
 
 import android.content.Context;
 import android.graphics.PixelFormat;
@@ -90,7 +91,8 @@
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                 PixelFormat.TRANSLUCENT);
         params.receiveInsetsIgnoringZOrder = true;
-        params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
+        params.privateFlags |=
+                PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION | SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         params.windowAnimations = android.R.style.Animation_Translucent;
         // Insets are configured to allow the menu to display over navigation and system bars.
         params.setFitInsetsTypes(0);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index 46d7d5f..428dc6e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -80,7 +80,6 @@
             MutableStateFlow(WindowInsets.Builder().build())
         // Root view of the Volume Dialog.
         val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
-        root.alpha = 0f
 
         animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
index 9d9fb9c..6ad2128 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
@@ -30,9 +30,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -51,11 +48,8 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
 import android.window.InputTransferToken;
 
-import androidx.annotation.NonNull;
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
@@ -80,19 +74,17 @@
 @RunWith(AndroidTestingRunner.class)
 @FlakyTest(bugId = 385115361)
 public class FullscreenMagnificationControllerTest extends SysuiTestCase {
-    private static final long ANIMATION_DURATION_MS = 100L;
     private static final long WAIT_TIMEOUT_S = 5L * HW_TIMEOUT_MULTIPLIER;
-    private static final long ANIMATION_TIMEOUT_MS =
-            5L * ANIMATION_DURATION_MS * HW_TIMEOUT_MULTIPLIER;
 
     private static final String UNIQUE_DISPLAY_ID_PRIMARY = "000";
     private static final String UNIQUE_DISPLAY_ID_SECONDARY = "111";
     private static final int CORNER_RADIUS_PRIMARY = 10;
     private static final int CORNER_RADIUS_SECONDARY = 20;
+    private static final int DISABLED = 0;
+    private static final int ENABLED = 3;
 
     private FullscreenMagnificationController mFullscreenMagnificationController;
     private SurfaceControlViewHost mSurfaceControlViewHost;
-    private ValueAnimator mShowHideBorderAnimator;
     private SurfaceControl.Transaction mTransaction;
     private TestableWindowManager mWindowManager;
     @Mock
@@ -136,7 +128,6 @@
         mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
 
         mTransaction = new SurfaceControl.Transaction();
-        mShowHideBorderAnimator = spy(newNullTargetObjectAnimator());
         mFullscreenMagnificationController = new FullscreenMagnificationController(
                 mContext,
                 mContext.getMainThreadHandler(),
@@ -146,141 +137,68 @@
                 mContext.getSystemService(WindowManager.class),
                 mIWindowManager,
                 scvhSupplier,
-                mTransaction,
-                mShowHideBorderAnimator);
+                mTransaction);
     }
 
     @After
     public void tearDown() {
-        getInstrumentation().runOnMainSync(
-                () -> mFullscreenMagnificationController
-                        .onFullscreenMagnificationActivationChanged(false));
+        getInstrumentation().runOnMainSync(() ->
+                mFullscreenMagnificationController.cleanUpBorder());
     }
 
     @Test
-    public void enableFullscreenMagnification_visibleBorder()
+    public void createShowTargetAnimator_runAnimator_alphaIsEqualToOne() {
+        View view = new View(mContext);
+        view.setAlpha(0f);
+        ValueAnimator animator = mFullscreenMagnificationController.createShowTargetAnimator(view);
+        animator.end();
+        assertThat(view.getAlpha()).isEqualTo(1f);
+    }
+
+    @Test
+    public void createHideTargetAnimator_runAnimator_alphaIsEqualToZero() {
+        View view = new View(mContext);
+        view.setAlpha(1f);
+        ValueAnimator animator = mFullscreenMagnificationController.createHideTargetAnimator(view);
+        animator.end();
+        assertThat(view.getAlpha()).isEqualTo(0f);
+    }
+
+    @Test
+    public void enableFullscreenMagnification_stateEnabled()
             throws InterruptedException, RemoteException {
-        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
-        CountDownLatch animationEndLatch = new CountDownLatch(1);
-        mTransaction.addTransactionCommittedListener(
-                Runnable::run, transactionCommittedLatch::countDown);
-        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                animationEndLatch.countDown();
-            }
-        });
-        getInstrumentation().runOnMainSync(() ->
-                //Enable fullscreen magnification
-                mFullscreenMagnificationController
-                        .onFullscreenMagnificationActivationChanged(true));
-        assertWithMessage("Failed to wait for transaction committed")
-                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
-                .isTrue();
-        assertWithMessage("Failed to wait for animation to be finished")
-                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
-                .isTrue();
-        verify(mShowHideBorderAnimator).start();
+        enableFullscreenMagnificationAndWaitForTransactionAndAnimation();
+
+        assertThat(mFullscreenMagnificationController.getState()).isEqualTo(ENABLED);
         verify(mIWindowManager)
                 .watchRotation(any(IRotationWatcher.class), eq(Display.DEFAULT_DISPLAY));
-        assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
     }
 
     @Test
-    public void disableFullscreenMagnification_reverseAnimationAndReleaseScvh()
+    public void disableFullscreenMagnification_stateDisabled()
             throws InterruptedException, RemoteException {
-        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
-        CountDownLatch enableAnimationEndLatch = new CountDownLatch(1);
-        CountDownLatch disableAnimationEndLatch = new CountDownLatch(1);
-        mTransaction.addTransactionCommittedListener(
-                Runnable::run, transactionCommittedLatch::countDown);
-        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
-                if (isReverse) {
-                    disableAnimationEndLatch.countDown();
-                } else {
-                    enableAnimationEndLatch.countDown();
-                }
-            }
+        enableFullscreenMagnificationAndWaitForTransactionAndAnimation();
+
+        getInstrumentation().runOnMainSync(() -> {
+            // Disable fullscreen magnification
+            mFullscreenMagnificationController
+                    .onFullscreenMagnificationActivationChanged(false);
         });
-        getInstrumentation().runOnMainSync(() ->
-                //Enable fullscreen magnification
-                mFullscreenMagnificationController
-                        .onFullscreenMagnificationActivationChanged(true));
-        assertWithMessage("Failed to wait for transaction committed")
-                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
-                .isTrue();
-        assertWithMessage("Failed to wait for enabling animation to be finished")
-                .that(enableAnimationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
-                .isTrue();
-        verify(mShowHideBorderAnimator).start();
+        waitForIdleSync();
+        assertThat(mFullscreenMagnificationController.mShowHideBorderAnimator).isNotNull();
+        mFullscreenMagnificationController.mShowHideBorderAnimator.end();
+        waitForIdleSync();
 
-        getInstrumentation().runOnMainSync(() ->
-                // Disable fullscreen magnification
-                mFullscreenMagnificationController
-                        .onFullscreenMagnificationActivationChanged(false));
-
-        assertWithMessage("Failed to wait for disabling animation to be finished")
-                .that(disableAnimationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
-                .isTrue();
-        verify(mShowHideBorderAnimator).reverse();
+        assertThat(mFullscreenMagnificationController.getState()).isEqualTo(DISABLED);
         verify(mSurfaceControlViewHost).release();
         verify(mIWindowManager).removeRotationWatcher(any(IRotationWatcher.class));
     }
 
     @Test
-    public void onFullscreenMagnificationActivationChangeTrue_deactivating_reverseAnimator()
-            throws InterruptedException {
-        // Simulate the hiding border animation is running
-        when(mShowHideBorderAnimator.isRunning()).thenReturn(true);
-        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
-        CountDownLatch animationEndLatch = new CountDownLatch(1);
-        mTransaction.addTransactionCommittedListener(
-                Runnable::run, transactionCommittedLatch::countDown);
-        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                animationEndLatch.countDown();
-            }
-        });
-
-        getInstrumentation().runOnMainSync(
-                () -> mFullscreenMagnificationController
-                            .onFullscreenMagnificationActivationChanged(true));
-
-        assertWithMessage("Failed to wait for transaction committed")
-                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
-                .isTrue();
-        assertWithMessage("Failed to wait for animation to be finished")
-                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
-                        .isTrue();
-        verify(mShowHideBorderAnimator).reverse();
-    }
-
-    @Test
     public void onScreenSizeChanged_activated_borderChangedToExpectedSize()
             throws InterruptedException {
-        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
-        CountDownLatch animationEndLatch = new CountDownLatch(1);
-        mTransaction.addTransactionCommittedListener(
-                Runnable::run, transactionCommittedLatch::countDown);
-        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                animationEndLatch.countDown();
-            }
-        });
-        getInstrumentation().runOnMainSync(() ->
-                //Enable fullscreen magnification
-                mFullscreenMagnificationController
-                        .onFullscreenMagnificationActivationChanged(true));
-        assertWithMessage("Failed to wait for transaction committed")
-                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
-                .isTrue();
-        assertWithMessage("Failed to wait for animation to be finished")
-                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
-                .isTrue();
+        enableFullscreenMagnificationAndWaitForTransactionAndAnimation();
+
         final Rect testWindowBounds = new Rect(
                 mWindowManager.getCurrentWindowMetrics().getBounds());
         testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
@@ -304,29 +222,8 @@
     @Test
     public void enableFullscreenMagnification_applyPrimaryCornerRadius()
             throws InterruptedException {
-        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
-        CountDownLatch animationEndLatch = new CountDownLatch(1);
-        mTransaction.addTransactionCommittedListener(
-                Runnable::run, transactionCommittedLatch::countDown);
-        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                animationEndLatch.countDown();
-            }
-        });
+        enableFullscreenMagnificationAndWaitForTransactionAndAnimation();
 
-        getInstrumentation().runOnMainSync(() ->
-                //Enable fullscreen magnification
-                mFullscreenMagnificationController
-                        .onFullscreenMagnificationActivationChanged(true));
-        assertWithMessage("Failed to wait for transaction committed")
-                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
-                .isTrue();
-        assertWithMessage("Failed to wait for animation to be finished")
-                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
-                .isTrue();
-
-        // Verify the initial corner radius is applied
         GradientDrawable backgroundDrawable =
                 (GradientDrawable) mSurfaceControlViewHost.getView().getBackground();
         assertThat(backgroundDrawable.getCornerRadius()).isEqualTo(CORNER_RADIUS_PRIMARY);
@@ -334,28 +231,8 @@
 
     @EnableFlags(Flags.FLAG_UPDATE_CORNER_RADIUS_ON_DISPLAY_CHANGED)
     @Test
-    public void onDisplayChanged_updateCornerRadiusToSecondary() throws InterruptedException {
-        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
-        CountDownLatch animationEndLatch = new CountDownLatch(1);
-        mTransaction.addTransactionCommittedListener(
-                Runnable::run, transactionCommittedLatch::countDown);
-        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                animationEndLatch.countDown();
-            }
-        });
-
-        getInstrumentation().runOnMainSync(() ->
-                //Enable fullscreen magnification
-                mFullscreenMagnificationController
-                        .onFullscreenMagnificationActivationChanged(true));
-        assertWithMessage("Failed to wait for transaction committed")
-                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
-                .isTrue();
-        assertWithMessage("Failed to wait for animation to be finished")
-                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
-                .isTrue();
+    public void onDisplayChanged_applyCornerRadiusToBorder() throws InterruptedException {
+        enableFullscreenMagnificationAndWaitForTransactionAndAnimation();
 
         ArgumentCaptor<DisplayManager.DisplayListener> displayListenerCaptor =
                 ArgumentCaptor.forClass(DisplayManager.DisplayListener.class);
@@ -372,22 +249,34 @@
                 .addOverride(
                         com.android.internal.R.dimen.rounded_corner_radius,
                         CORNER_RADIUS_SECONDARY);
+
         getInstrumentation().runOnMainSync(() ->
                 displayListenerCaptor.getValue().onDisplayChanged(Display.DEFAULT_DISPLAY));
         waitForIdleSync();
+
         // Verify the corner radius is updated
         GradientDrawable backgroundDrawable2 =
                 (GradientDrawable) mSurfaceControlViewHost.getView().getBackground();
         assertThat(backgroundDrawable2.getCornerRadius()).isEqualTo(CORNER_RADIUS_SECONDARY);
     }
 
+    private void enableFullscreenMagnificationAndWaitForTransactionAndAnimation()
+            throws InterruptedException {
+        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+        mTransaction.addTransactionCommittedListener(
+                Runnable::run, transactionCommittedLatch::countDown);
 
-    private ValueAnimator newNullTargetObjectAnimator() {
-        final ValueAnimator animator =
-                ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
-        Interpolator interpolator = new DecelerateInterpolator(2.5f);
-        animator.setInterpolator(interpolator);
-        animator.setDuration(ANIMATION_DURATION_MS);
-        return animator;
+        getInstrumentation().runOnMainSync(() ->
+                //Enable fullscreen magnification
+                mFullscreenMagnificationController
+                        .onFullscreenMagnificationActivationChanged(true));
+
+        assertWithMessage("Failed to wait for transaction committed")
+                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
+                .isTrue();
+        waitForIdleSync();
+        assertThat(mFullscreenMagnificationController.mShowHideBorderAnimator).isNotNull();
+        mFullscreenMagnificationController.mShowHideBorderAnimator.end();
+        waitForIdleSync();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 856c379..9f6ad56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -82,7 +82,7 @@
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
-        final SecureSettings secureSettings = TestUtils.mockSecureSettings();
+        final SecureSettings secureSettings = TestUtils.mockSecureSettings(mContext);
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 secureSettings, mHearingAidDeviceManager);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 33cfb38..1500340 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -144,7 +144,7 @@
     private HearingAidDeviceManager mHearingAidDeviceManager;
     @Mock
     private PackageManager mMockPackageManager;
-    private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
+    private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings(mContext);
 
     private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index ef6f923..12c8f9c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2538,7 +2538,7 @@
 
     void wakeUp(int displayId, String reason) {
         mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_APPLICATION,
-                "android.server.am:TURN_ON:" + reason, displayId);
+                "android.server.wm:TURN_ON:" + reason, displayId);
     }
 
     /** Starts a batch of visibility updates. */
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9c1cf6e..fe478c60 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5230,9 +5230,15 @@
             // to ensure any necessary pause logic occurs. In the case where the Activity will be
             // shown regardless of the lock screen, the call to
             // {@link ActivityTaskSupervisor#checkReadyForSleepLocked} is skipped.
-            final ActivityRecord next = topRunningActivity(true /* focusableOnly */);
-            if (next == null || !next.canTurnScreenOn()) {
-                checkReadyForSleep();
+            if (shouldSleepActivities()) {
+                final ActivityRecord next = topRunningActivity(true /* focusableOnly */);
+                if (next != null && next.canTurnScreenOn()
+                        && !mWmService.mPowerManager.isInteractive()) {
+                    mTaskSupervisor.wakeUp(getDisplayId(), "resumeTop-turnScreenOnFlag");
+                    next.setCurrentLaunchCanTurnScreenOn(false);
+                } else {
+                    checkReadyForSleep();
+                }
             }
         } finally {
             mInResumeTopActivity = false;